This PR allows to send binaries (and whatever writable document) as bodies in nitcorn's responses.
The reduced use of buffers and copies should also reduce the pressure on the GC and make things faster.
Embrace, extend and extinguish #2703
Pull-Request: #2704
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
end
var response = new HttpResponse(200)
- response.body = template.write_to_string
+ response.body = template
return response
end
end
fun to_http_response: HttpResponse
do
var resp = new HttpResponse(200)
- resp.body = tpl_page.write_to_string
+ resp.body = tpl_page
return resp
end
end
var page = new NitRpgPage(root_url)
var error = new ErrorPanel(msg)
page.flow_panels.add error
- rsp.body = page.write_to_string
+ rsp.body = page
return rsp
end
page = new NitRpgPage(root_url)
page.side_panels.add new GamesShortListPanel(root_url, games)
page.flow_panels.add new MDPanel(readme)
- response.body = page.write_to_string
+ response.body = page
return response
end
page.breadcrumbs = new Breadcrumbs
page.breadcrumbs.add_link(root_url / "games", "games")
page.flow_panels.add new GamesListPanel(root_url, games)
- response.body = page.write_to_string
+ response.body = page
return response
end
end
page.flow_panels.add new PodiumPanel(game)
page.flow_panels.add new EventListPanel(game, list_limit, list_from)
page.flow_panels.add new AchievementsListPanel(game)
- rsp.body = page.write_to_string
+ rsp.body = page
return rsp
end
end
if is_response_error(rsp) then return rsp
page.breadcrumbs.add_link(game.url / "players", "players")
page.flow_panels.add new ListPlayersPanel(game)
- rsp.body = page.write_to_string
+ rsp.body = page
return rsp
end
end
page.flow_panels.add new PlayerWorkPanel(game, player)
page.flow_panels.add new AchievementsListPanel(player)
page.flow_panels.add new EventListPanel(player, list_limit, list_from)
- rsp.body = page.write_to_string
+ rsp.body = page
return rsp
end
end
if is_response_error(rsp) then return rsp
page.breadcrumbs.add_link(game.url / "achievements", "achievements")
page.flow_panels.add new AchievementsListPanel(game)
- rsp.body = page.write_to_string
+ rsp.body = page
return rsp
end
end
page.breadcrumbs.add_link(achievement.url, achievement.name)
page.flow_panels.add new AchievementPanel(achievement)
page.flow_panels.add new EventListPanel(achievement, list_limit, list_from)
- rsp.body = page.write_to_string
+ rsp.body = page
return rsp
end
end
# TODO: Add a specific body to the bad request page.
fun bad_req: HttpResponse do
var rsp = new HttpResponse(400)
- rsp.body = (new OpportunityHomePage).write_to_string
+ rsp.body = new OpportunityHomePage
return rsp
end
end
meetpage.ans = ansset
meetpage.meet = meet
meetpage.error = "'Meetup name' is a mandatory fields."
- rsp.body = meetpage.write_to_string
+ rsp.body = meetpage
return rsp
end
var meetpage = new MeetupCreationPage
meetpage.meet = meet
meetpage.error = "You need to input at least one answer."
- rsp.body = meetpage.write_to_string
+ rsp.body = meetpage
return rsp
end
<p>Failed to create event</p>
<p>This is a server side error, it has been logged.
You may still want to contact the maintainers of this website.</p>"""
- rsp.body = meetpage.write_to_string
+ rsp.body = meetpage
return rsp
end
end
db.close
var rsp = new HttpResponse(200)
- rsp.body = (new MeetupConfirmation(meet)).write_to_string
+ rsp.body = new MeetupConfirmation(meet)
return rsp
end
if rq.has("new_meetup") then
var rsp = new HttpResponse(200)
var page = new MeetupCreationPage
- rsp.body = page.write_to_string
+ rsp.body = page
return rsp
end
if get.has_key("meetup_id") then
var rsp = new HttpResponse(200)
- rsp.body = (new OpportunityMeetupPage.from_id(get["meetup_id"])).write_to_string
+ rsp.body = new OpportunityMeetupPage.from_id(get["meetup_id"])
return rsp
end
var rsp = new HttpResponse(200)
- rsp.body = (new OpportunityHomePage).write_to_string
+ rsp.body = new OpportunityHomePage
return rsp
end
# The specific logic it let to the concrete subclasses
fun write_to(stream: Writer) is abstract
- # Like `write_to` but return a new String (may be quite large)
+ # Like `write_to` but return a new String (may be quite large).
#
- # This funtionality is anectodical, since the point
- # of streamable object to to be efficienlty written to a
- # stream without having to allocate and concatenate strings
+ # This functionality is anecdotal, since the point
+ # of a streamable object is to be efficiently written to a
+ # stream without having to allocate and concatenate strings.
fun write_to_string: String
do
var stream = new StringWriter
write_to(stream)
return stream.to_s
end
+
+ # Like `write_to` but return a new Bytes (may be quite large)
+ #
+ # This functionality is anecdotal, since the point
+ # of a streamable object is to be efficiently written to a
+ # stream without having to allocate and concatenate buffers.
+ #
+ # Nevertheless, you might need this method if you want to know
+ # the byte size of a writable object.
+ fun write_to_bytes: Bytes
+ do
+ var stream = new BytesWriter
+ write_to(stream)
+ return stream.bytes
+ end
end
redef class Bytes
if response.status_code != 200 then
var tmpl = error_page(response.status_code)
if header != null and tmpl isa ErrorTemplate then tmpl.header = header
- response.body = tmpl.to_s
+ response.body = tmpl
end
return response
module http_response
import serialization
+private import template
# A response to send over HTTP
class HttpResponse
var header = new HashMap[String, String]
# Body of this response
- var body = "" is writable
+ var body: Writable = "" is writable
# Files appended after `body`
var files = new Array[String]
# Set the content length if not already set
if not header.keys.has("Content-Length") then
# Size of the body
- var len = body.byte_length
+ var len
+ var body = self.body
+ if body isa Text then
+ len = body.byte_length
+ else if body isa Bytes then
+ len = body.length
+ else
+ # We need the length, but there is no length in a writable.
+ # So just render it as a bytes then measure :/
+ body = body.write_to_bytes
+ len = body.length
+ # Keep the body as bytes since we have it
+ self.body = body
+ end
# Size of included files
for path in files do
end
# Get this reponse as a string according to HTTP protocol
- redef fun to_s
+ fun render: Writable
do
finalize
- var buf = new FlatBuffer
- buf.append("{http_version} {status_code} {status_message or else ""}\r\n")
+ var buf = new Template
+ buf.add("{http_version} {status_code} {status_message or else ""}\r\n")
for key, value in header do
- buf.append("{key}: {value}\r\n")
+ buf.add("{key}: {value}\r\n")
end
- buf.append("\r\n{body}")
- return buf.to_s
+ buf.add("\r\n")
+ buf.add body
+ return buf
end
end
# Send back `response` to the client
fun respond(response: HttpResponse)
do
- write response.to_s
+ response.render.write_to(self)
for path in response.files do write_file path
end
end
# Write data in body response and send it.
fun send(raw_data: nullable Writable, status: nullable Int) do
if raw_data != null then
- body += raw_data.write_to_string
+ body = raw_data
end
if status != null then
status_code = status
Server: nitcorn\r
Set-Cookie: nitcorn_session=; HttpOnly; expires=Thu, 01 Jan 1970 00:00:00 GMT\r
\r
+
+[Client] curl -s localhost:*****/simple_binary/
+ñòó
+
+[Client] curl -s localhost:*****/simple_template/
+Hello
+ñòó
redef fun answer(request, turi)
do
var rep = new HttpResponse(200)
- rep.body = """
+ var body = """
[Response] Simple answer
Method: {{{request.method}}}, URI: {{{request.uri}}}, trailing: {{{turi}}}"""
if request.get_args.not_empty
- then rep.body += "\nGET args: {request.get_args.join(", ", ":")}"
+ then body += "\nGET args: {request.get_args.join(", ", ":")}"
if request.post_args.not_empty
- then rep.body += "\nPOST args: {request.post_args.join(", ", ":")}"
+ then body += "\nPOST args: {request.post_args.join(", ", ":")}"
if request.uri_params.not_empty
- then rep.body += "\nParams args: {request.uri_params.join(", ", ":")}"
+ then body += "\nParams args: {request.uri_params.join(", ", ":")}"
if request.cookie.not_empty
- then rep.body += "\nCookie: {request.cookie.join(", ", ":")}"
+ then body += "\nCookie: {request.cookie.join(", ", ":")}"
- rep.body += "\n"
+ body += "\n"
+ rep.body = body
+ return rep
+ end
+end
+
+class MyBinAction
+ super Action
+
+ redef fun answer(request, turi)
+ do
+ var rep = new HttpResponse(200)
+ rep.body = b"\xF1\xF2\xF3\n"
+ return rep
+ end
+end
+
+class MyTmplAction
+ super Action
+
+ redef fun answer(request, turi)
+ do
+ var rep = new HttpResponse(200)
+ var body = new Template
+ body.add "Hello\n"
+ body.add b"\xF1\xF2\xF3\n"
+ rep.body = body
return rep
end
end
vh.routes.add new Route("file_server", new FileServer(fs_path.simplify_path))
vh.routes.add new Route("simple_answer", new MyAction)
vh.routes.add new Route("params_answer/:i/:s", new MyAction)
+ vh.routes.add new Route("simple_binary", new MyBinAction)
+ vh.routes.add new Route("simple_template", new MyTmplAction)
# Launch
var factory = new HttpFactory.and_libevent
system "curl -s {iface}/file_server/unknown_file.txt --head"
system "curl -s {iface}/invalid_route --head"
+
+ system "curl -s {iface}/simple_binary/"
+ system "curl -s {iface}/simple_template/"
return null
end