Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changed Response to use body: bytes #375

Merged
merged 9 commits into from
Feb 4, 2023
26 changes: 23 additions & 3 deletions integration_tests/base_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ async def redirect_route(request):

@app.get("/types/response")
def response_type(request):
return Response(status_code=200, headers={}, body="OK")
return Response(status_code=200, headers={}, body=b"OK")


@app.get("/types/str")
Expand All @@ -249,7 +249,7 @@ def int_type(request):

@app.get("/async/types/response")
async def async_response_type(request):
return Response(status_code=200, headers={}, body="OK")
return Response(status_code=200, headers={}, body=b"OK")


@app.get("/async/types/str")
Expand All @@ -270,12 +270,32 @@ def file_download_sync():


@app.get("/file_download_async")
def file_download_async():
async def file_download_async():
current_file_path = pathlib.Path(__file__).parent.resolve()
file_path = os.path.join(current_file_path, "downloads", "test.txt")
return serve_file(file_path)


@app.get("/binary_output_sync")
def binary_output(request):
return b"OK"
# response = Response(
# status_code=200,
# headers={"Content-Type": "application/octet-stream"},
# body=b"OK",
# )


@app.get("/binary_output_async")
async def binary_output(request):
return b"OK"
# response = Response(
# status_code=200,
# headers={"Content-Type": "application/octet-stream"},
# body=b"OK",
# )
madhavajay marked this conversation as resolved.
Show resolved Hide resolved


if __name__ == "__main__":
app.add_request_header("server", "robyn")
current_file_path = pathlib.Path(__file__).parent.resolve()
Expand Down
17 changes: 17 additions & 0 deletions integration_tests/test_binary_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import requests

BASE_URL = "http://127.0.0.1:8080"


def test_file_download_sync(session):
r = requests.get(f"{BASE_URL}/binary_output_sync")
assert r.status_code == 200
assert r.headers["Content-Type"] == "application/octet-stream"
assert r.text == "OK"


def test_file_download_async(session):
r = requests.get(f"{BASE_URL}/binary_output_async")
assert r.status_code == 200
assert r.headers["Content-Type"] == "application/octet-stream"
assert r.text == "OK"
16 changes: 12 additions & 4 deletions robyn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ def add_directory(
index_file: Optional[str] = None,
show_files_listing: bool = False,
):
self.directories.append(Directory(route, directory_path, index_file, show_files_listing))
self.directories.append(
Directory(route, directory_path, index_file, show_files_listing)
)

def add_request_header(self, key: str, value: str) -> None:
self.request_headers.append(Header(key, value))
Expand Down Expand Up @@ -115,7 +117,9 @@ def start(self, url: str = "127.0.0.1", port: int = 8080):
url = os.getenv("ROBYN_URL", url)
port = int(os.getenv("ROBYN_PORT", port))

logger.info("%sStarting server at %s:%s %s", Colors.OKGREEN, url, port, Colors.ENDC)
logger.info(
"%sStarting server at %s:%s %s", Colors.OKGREEN, url, port, Colors.ENDC
)

def init_processpool(socket):

Expand Down Expand Up @@ -163,7 +167,9 @@ def init_processpool(socket):
process_pool = init_processpool(socket)

def terminating_signal_handler(_sig, _frame):
logger.info(f"{Colors.BOLD}{Colors.OKGREEN} Terminating server!! {Colors.ENDC}")
logger.info(
f"{Colors.BOLD}{Colors.OKGREEN} Terminating server!! {Colors.ENDC}"
)
for process in process_pool:
process.kill()

Expand All @@ -176,7 +182,9 @@ def terminating_signal_handler(_sig, _frame):
else:
event_handler = EventHandler(self.file_path)
event_handler.start_server_first_time()
logger.info(f"{Colors.OKBLUE}Dev server initialised with the directory_path : {self.directory_path}{Colors.ENDC}")
logger.info(
f"{Colors.OKBLUE}Dev server initialised with the directory_path : {self.directory_path}{Colors.ENDC}"
)
observer = Observer()
observer.schedule(event_handler, path=self.directory_path, recursive=True)
observer.start()
Expand Down
3 changes: 1 addition & 2 deletions robyn/robyn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ class FunctionInfo:
class Response:
status_code: int
headers: dict[str, str]
body: str
body: bytes

def set_file_path(self, file_path: str):
pass


class Server:
def __init__(self) -> None:
pass
Expand Down
19 changes: 15 additions & 4 deletions robyn/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def _format_response(self, res):
if type(res) == dict:
status_code = res.get("status_code", 200)
headers = res.get("headers", {"Content-Type": "text/plain"})
body = res.get("body", "")
body = str(res.get("body", "")).encode("utf-8")
madhavajay marked this conversation as resolved.
Show resolved Hide resolved

if type(status_code) != int:
status_code = int(status_code) # status_code can potentially be string
Expand All @@ -39,12 +39,23 @@ def _format_response(self, res):
response.set_file_path(file_path)
elif type(res) == Response:
response = res
elif type(res) == bytes:
response = Response(
status_code=200,
headers={"Content-Type": "application/octet-stream"},
body=res,
)
else:
response = Response(status_code=200, headers={"Content-Type": "text/plain"}, body=str(res))

response = Response(
status_code=200,
headers={"Content-Type": "text/plain"},
body=str(res).encode("utf-8"),
)
return response

def add_route(self, route_type: str, endpoint: str, handler: Callable, is_const: bool) -> Union[Callable, CoroutineType]:
def add_route(
self, route_type: str, endpoint: str, handler: Callable, is_const: bool
) -> Union[Callable, CoroutineType]:
@wraps(handler)
async def async_inner_handler(*args):
response = self._format_response(await handler(*args))
Expand Down
71 changes: 67 additions & 4 deletions src/types.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,75 @@
use core::{mem};
use std::ops::{Deref, DerefMut};
use std::{
convert::Infallible,
task::{Context, Poll},
pin::Pin,
};
use std::collections::HashMap;

use actix_web::web::Bytes;
use actix_web::{http::Method, HttpRequest};
use actix_http::body::MessageBody;
use actix_http::body::BodySize;

use anyhow::Result;
use dashmap::DashMap;
use pyo3::{exceptions, prelude::*};
use pyo3::types::PyBytes;

use crate::io_helpers::read_file;


#[derive(Debug, Clone)]
#[pyclass]
pub struct ActixBytesWrapper(Bytes);

impl ActixBytesWrapper {
pub fn new(bytes: &PyBytes) -> Self {
Self(Bytes::from(bytes.as_bytes().to_vec()))
}
madhavajay marked this conversation as resolved.
Show resolved Hide resolved
}

impl Deref for ActixBytesWrapper {
type Target = Bytes;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for ActixBytesWrapper {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl MessageBody for ActixBytesWrapper {
type Error = Infallible;

#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}

#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(mem::take(self.get_mut()))))
}
}

#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(self.0)
}
}

#[pyclass]
#[derive(Debug, Clone)]
pub struct FunctionInfo {
Expand Down Expand Up @@ -78,20 +140,20 @@ pub struct Response {
pub status_code: u16,
pub response_type: String,
pub headers: HashMap<String, String>,
pub body: String,
pub body: ActixBytesWrapper,
pub file_path: Option<String>,
}

#[pymethods]
impl Response {
#[new]
pub fn new(status_code: u16, headers: HashMap<String, String>, body: String) -> Self {
pub fn new(status_code: u16, headers: HashMap<String, String>, body: &PyBytes) -> Self {
madhavajay marked this conversation as resolved.
Show resolved Hide resolved
Self {
status_code,
// we should be handling based on headers but works for now
response_type: "text".to_string(),
headers,
body,
body: ActixBytesWrapper::new(body),
file_path: None,
}
}
Expand All @@ -100,10 +162,11 @@ impl Response {
// we should be handling based on headers but works for now
self.response_type = "static_file".to_string();
self.file_path = Some(file_path.to_string());
self.body = match read_file(file_path) {
let string = match read_file(file_path) {
madhavajay marked this conversation as resolved.
Show resolved Hide resolved
Ok(b) => b,
Err(e) => return Err(exceptions::PyIOError::new_err::<String>(e.to_string())),
};
self.body = ActixBytesWrapper(Bytes::from(string.clone()));
madhavajay marked this conversation as resolved.
Show resolved Hide resolved
Ok(())
}
}
Expand Down