diff --git a/contrib/library/server/nino b/contrib/library/server/nino index 59505ccd..8b4f774b 160000 --- a/contrib/library/server/nino +++ b/contrib/library/server/nino @@ -1 +1 @@ -Subproject commit 59505ccdc45fc17c67b9f911b428516364ed7930 +Subproject commit 8b4f774bab8484f0927b6ad5ce1989450be988b4 diff --git a/examples/hello_routed_world/hello-safe.ecf b/examples/hello_routed_world/hello-safe.ecf index 90db7a6b..87f0ed36 100644 --- a/examples/hello_routed_world/hello-safe.ecf +++ b/examples/hello_routed_world/hello-safe.ecf @@ -1,24 +1,28 @@ - + /EIFGENs$ /\.git$ /\.svn$ - - + - - + + diff --git a/examples/hello_routed_world/src/hello_routed_world.e b/examples/hello_routed_world/src/hello_routed_world.e index 14515018..5ab38b7b 100644 --- a/examples/hello_routed_world/src/hello_routed_world.e +++ b/examples/hello_routed_world/src/hello_routed_world.e @@ -8,24 +8,21 @@ class HELLO_ROUTED_WORLD inherit - ANY - URI_TEMPLATE_ROUTED_SERVICE ROUTED_SERVICE_HELPER - DEFAULT_SERVICE - create make feature {NONE} -- Initialization - make + local + s: DEFAULT_SERVICE do initialize_router - make_and_launch + create s.make_and_launch (agent execute) end create_router @@ -90,7 +87,11 @@ feature -- Execution n: INTEGER i: INTEGER s: STRING_8 + df: WSF_FILE_RESPONSE do + create df.make_html ("index.html") + df.set_no_cache + l_url := req.script_url ("/home") n := 3 @@ -199,7 +200,7 @@ feature -- Execution handle_hello (ctx: REQUEST_URI_TEMPLATE_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE) do - execute_hello (req, res, Void, ctx) + execute_hello (req, res, ctx.string_parameter ("name"), ctx) end handle_anonymous_hello (ctx: REQUEST_URI_TEMPLATE_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE) diff --git a/examples/restbucks/src/restbucks_server.e b/examples/restbucks/src/restbucks_server.e index be9f9da5..f1ea442a 100644 --- a/examples/restbucks/src/restbucks_server.e +++ b/examples/restbucks/src/restbucks_server.e @@ -14,17 +14,17 @@ inherit ROUTED_SERVICE_HELPER - DEFAULT_SERVICE - create make feature {NONE} -- Initialization make + local + s: DEFAULT_SERVICE do initialize_router - make_and_launch + create s.make_and_launch (agent execute) end create_router diff --git a/library/protocol/http/src/http_header.e b/library/protocol/http/src/http_header.e index 6221778e..19385b1c 100644 --- a/library/protocol/http/src/http_header.e +++ b/library/protocol/http/src/http_header.e @@ -193,6 +193,12 @@ feature -- Content related header put_header_key_value ({HTTP_HEADER_NAMES}.header_transfer_encoding, a_enc) end + put_transfer_encoding_binary + -- Put "Transfer-Encoding: binary" header + do + put_transfer_encoding ("binary") + end + put_transfer_encoding_chunked -- Put "Transfer-Encoding: chunked" header do diff --git a/library/server/ewsgi/specification/response/wgi_response.e b/library/server/ewsgi/specification/response/wgi_response.e index 8f814bae..672e6f1f 100644 --- a/library/server/ewsgi/specification/response/wgi_response.e +++ b/library/server/ewsgi/specification/response/wgi_response.e @@ -6,7 +6,7 @@ note deferred class WGI_RESPONSE -feature {WGI_SERVICE} -- Commit +feature {WGI_RESPONSE, WGI_SERVICE} -- Commit commit -- Commit the current response @@ -110,13 +110,6 @@ feature -- Output operation deferred end - write_file_content (fn: READABLE_STRING_8) - -- Send the content of file `fn' - require - message_writable: message_writable - deferred - end - flush -- Flush if it makes sense deferred diff --git a/library/server/ewsgi/specification/stream/wgi_output_stream.e b/library/server/ewsgi/specification/stream/wgi_output_stream.e index de11fcb1..4e7e85fc 100644 --- a/library/server/ewsgi/specification/stream/wgi_output_stream.e +++ b/library/server/ewsgi/specification/stream/wgi_output_stream.e @@ -43,25 +43,6 @@ feature -- Basic operation put_string (s.substring (start_index, end_index)) end - put_file_content (fn: STRING) - -- Send the content of file `fn' - local - f: RAW_FILE - do - create f.make (fn) - if f.exists and then f.is_readable then - f.open_read - from - until - f.exhausted - loop - f.read_stream (4096) - put_string (f.last_string) - end - f.close - end - end - put_header_line (s: STRING) -- Send `s' to http client as header line do diff --git a/library/server/ewsgi/src/helper/wgi_response_stream.e b/library/server/ewsgi/src/helper/wgi_response_stream.e index 33c35e51..f6137fc3 100644 --- a/library/server/ewsgi/src/helper/wgi_response_stream.e +++ b/library/server/ewsgi/src/helper/wgi_response_stream.e @@ -115,12 +115,6 @@ feature -- Output operation output.put_substring (s, start_index, end_index) end - write_file_content (fn: READABLE_STRING_8) - -- Send the content of file `fn' - do - output.put_file_content (fn) - end - flush do output.flush diff --git a/library/server/ewsgi/src/wgi_output_stream.e b/library/server/ewsgi/src/wgi_output_stream.e index d5b0ab14..7620e4e2 100644 --- a/library/server/ewsgi/src/wgi_output_stream.e +++ b/library/server/ewsgi/src/wgi_output_stream.e @@ -46,28 +46,6 @@ feature -- Output put_string (c.out) end - put_file_content (fn: READABLE_STRING_8) - -- Send the content of file `fn' - require - string_not_empty: not fn.is_empty - is_readable: (create {RAW_FILE}.make (fn)).is_readable - local - f: RAW_FILE - do - create f.make (fn) - check f.exists and then f.is_readable end - - f.open_read - from - until - f.exhausted - loop - f.read_stream (4096) - put_string (f.last_string) - end - f.close - end - feature -- Specific output put_header_line (s: READABLE_STRING_8) diff --git a/library/server/libfcgi/interface/fcgi_i.e b/library/server/libfcgi/interface/fcgi_i.e index 7010e48b..b568f197 100644 --- a/library/server/libfcgi/interface/fcgi_i.e +++ b/library/server/libfcgi/interface/fcgi_i.e @@ -120,7 +120,7 @@ feature -- Input feature -- Output - put_string (a_str: STRING) + put_string (a_str: READABLE_STRING_8) -- Put `a_str' on the FastCGI stdout. require a_str_not_void: a_str /= Void diff --git a/library/server/request/router/src/request_file_system_handler.e b/library/server/request/router/src/request_file_system_handler.e index 3f766391..8038259b 100644 --- a/library/server/request/router/src/request_file_system_handler.e +++ b/library/server/request/router/src/request_file_system_handler.e @@ -147,32 +147,21 @@ feature -- Execution respond_file (f: FILE; ctx: C; req: WSF_REQUEST; res: WSF_RESPONSE) local - fn: READABLE_STRING_8 h: HTTP_HEADER ext: READABLE_STRING_8 ct: detachable READABLE_STRING_8 + fres: WSF_FILE_RESPONSE do - fn := f.name - ext := extension (fn) + ext := extension (f.name) ct := extension_mime_mapping.mime_type (ext) - create h.make + if ct = Void then + ct := {HTTP_MIME_TYPES}.application_force_download + end + create fres.make_with_content_type (ct, f.name) + fres.set_status_code ({HTTP_STATUS_CODE}.ok) + fres.set_answer_head_request_method (req.request_method.same_string ({HTTP_REQUEST_METHODS}.method_head)) - if ct /= Void then - h.put_content_type (ct) - h.put_content_length (f.count) - res.set_status_code ({HTTP_STATUS_CODE}.ok) - res.write_header_text (h.string) - else - create h.make - h.put_content_type ({HTTP_MIME_TYPES}.application_force_download) - h.put_content_length (f.count) - res.set_status_code ({HTTP_STATUS_CODE}.ok) - res.write_header_text (h.string) - end - if not req.request_method.same_string ({HTTP_REQUEST_METHODS}.method_head) then - res.write_file_content (fn) - end - res.flush + res.put_response (fres) end respond_not_found (uri: READABLE_STRING_8; ctx: C; req: WSF_REQUEST; res: WSF_RESPONSE) diff --git a/library/server/wsf/default/cgi/default_service.e b/library/server/wsf/default/cgi/default_service.e index 425fe5ba..d93a14a0 100644 --- a/library/server/wsf/default/cgi/default_service.e +++ b/library/server/wsf/default/cgi/default_service.e @@ -3,22 +3,37 @@ note date: "$Date$" revision: "$Revision$" -deferred class +class DEFAULT_SERVICE inherit WSF_SERVICE +create + make_and_launch + feature {NONE} -- Initialization - make_and_launch + make_and_launch (a_action: like action) local cgi: WGI_CGI_CONNECTOR do + action := a_action create cgi.make (Current) cgi.launch end +feature -- Execution + + action: PROCEDURE [ANY, TUPLE [WSF_REQUEST, WSF_RESPONSE]] + -- Action to be executed on request incoming + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + do + action.call ([req, res]) + end + note copyright: "2011-2011, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/library/server/wsf/default/libfcgi/default_service.e b/library/server/wsf/default/libfcgi/default_service.e index 26b67e7a..8e5f34ca 100644 --- a/library/server/wsf/default/libfcgi/default_service.e +++ b/library/server/wsf/default/libfcgi/default_service.e @@ -3,22 +3,37 @@ note date: "$Date$" revision: "$Revision$" -deferred class +class DEFAULT_SERVICE inherit WSF_SERVICE +create + make_and_launch + feature {NONE} -- Initialization - make_and_launch + make_and_launch (a_action: like action) local conn: WGI_LIBFCGI_CONNECTOR do + action := a_action create conn.make (Current) conn.launch end +feature -- Execution + + action: PROCEDURE [ANY, TUPLE [WSF_REQUEST, WSF_RESPONSE]] + -- Action to be executed on request incoming + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + do + action.call ([req, res]) + end + note copyright: "2011-2011, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/library/server/wsf/default/nino/default_service.e b/library/server/wsf/default/nino/default_service.e index 0eb3c0d1..2364bf85 100644 --- a/library/server/wsf/default/nino/default_service.e +++ b/library/server/wsf/default/nino/default_service.e @@ -3,34 +3,48 @@ note date: "$Date$" revision: "$Revision$" -deferred class +class DEFAULT_SERVICE inherit WSF_SERVICE +create + make_and_launch + feature {NONE} -- Initialization - make_and_launch + make_and_launch (a_action: like action) local app: NINO_SERVICE + port_number: INTEGER + base_url: STRING do - port_number := 8080 + action := a_action + port_number := 80 --| Default, but quite often, this port is already used ... base_url := "" debug ("nino") print ("Example: start a Nino web server on port " + port_number.out + ", %Nand reply Hello World for any request such as http://localhost:" + port_number.out + "/" + base_url + "%N") end create app.make_custom (agent wgi_execute, base_url) + debug ("nino") + app.set_is_verbose (True) + end app.listen (port_number) end - port_number: INTEGER +feature -- Execution - base_url: STRING + action: PROCEDURE [ANY, TUPLE [WSF_REQUEST, WSF_RESPONSE]] + -- Action to be executed on request incoming + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + do + action.call ([req, res]) + end -invariant - port_number_valid: port_number > 0 note copyright: "2011-2011, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/library/server/wsf/src/response/wsf_download_response.e b/library/server/wsf/src/response/wsf_download_response.e new file mode 100644 index 00000000..350c6c61 --- /dev/null +++ b/library/server/wsf/src/response/wsf_download_response.e @@ -0,0 +1,26 @@ +note + description : "Objects that ..." + author : "$Author$" + date : "$Date$" + revision : "$Revision$" + +class + WSF_DOWNLOAD_RESPONSE + +inherit + WSF_FILE_RESPONSE + redefine + get_content_type + end + +create + make + +feature {NONE} -- Implementation + + get_content_type + do + content_type := {HTTP_MIME_TYPES}.application_force_download + end + +end diff --git a/library/server/wsf/src/response/wsf_file_response.e b/library/server/wsf/src/response/wsf_file_response.e new file mode 100644 index 00000000..4e910451 --- /dev/null +++ b/library/server/wsf/src/response/wsf_file_response.e @@ -0,0 +1,210 @@ +note + description : "Objects that ..." + author : "$Author$" + date : "$Date$" + revision : "$Revision$" + +class + WSF_FILE_RESPONSE + +inherit + WSF_RESPONSE_MESSAGE + +create + make, + make_with_content_type, + make_html + +feature {NONE} -- Initialization + + make (a_file_name: READABLE_STRING_8) + do + file_name := a_file_name + base_name := basename (a_file_name) + get_content_type + initialize + end + + make_with_content_type (a_content_type: READABLE_STRING_8; a_filename: READABLE_STRING_8) + -- Initialize `Current'. + do + file_name := a_filename + base_name := basename (a_filename) + content_type := a_content_type + initialize + end + + make_html (a_filename: READABLE_STRING_8) + -- Initialize `Current'. + do + make_with_content_type ({HTTP_MIME_TYPES}.text_html, a_filename) + end + + initialize + local + h: like header + do + create h.make + header := h + h.put_content_type (content_type) + h.put_transfer_encoding_binary + h.put_content_length (filesize (file_name)) + h.put_content_disposition ("attachment", "filename=%""+ base_name +"%"") + end + +feature -- Element change + + set_expires (t: INTEGER) + do + header.put_expires (t) + end + + set_no_cache + local + h: like header + do + h := header + h.put_expires (0) + h.put_cache_control ("no-cache, must-revalidate") + h.put_pragma_no_cache + end + +feature -- Access + + header: HTTP_HEADER + + status_code: INTEGER assign set_status_code + + file_name: READABLE_STRING_8 + + base_name: READABLE_STRING_8 + + content_type: READABLE_STRING_8 + +feature -- Settings + + answer_head_request_method: BOOLEAN assign set_answer_head_request_method + -- For HEAD request method, only http header should be sent + +feature -- Element change + + set_status_code (c: like status_code) + -- Set `status_code' to `c'. + require + valid_status_code: status_code > 0 + do + status_code := c + ensure + status_code_set: status_code = c + end + + set_answer_head_request_method (b: BOOLEAN) + -- Set answer_head_request_method' to `b'. + do + answer_head_request_method := b + end + +feature -- Basic operations + + send_to (res: WSF_RESPONSE) + do + res.set_status_code (status_code) + res.write_header_text (header.string) + if not answer_head_request_method then + send_file_content_to (file_name, res) + end + end + +feature {NONE} -- Implementation: file system helper + + filesize (fn: STRING): INTEGER + -- Size of the file `fn'. + local + f: RAW_FILE + do + create f.make (fn) + if f.exists then + Result := f.count + end + end + + file_extension (fn: STRING): STRING + -- Extension of file `fn'. + local + p: INTEGER + do + p := fn.last_index_of ('.', fn.count) + if p > 0 then + Result := fn.substring (p + 1, fn.count) + else + create Result.make_empty + end + end + + basename (fn: STRING): STRING + -- Basename of `fn'. + local + p: INTEGER + do + p := fn.last_index_of ((create {OPERATING_ENVIRONMENT}).Directory_separator, fn.count) + if p > 0 then + Result := fn.substring (p + 1, fn.count) + else + Result := fn + end + end + + dirname (fn: STRING): STRING + -- Dirname of `fn'. + local + p: INTEGER + do + p := fn.last_index_of ((create {OPERATING_ENVIRONMENT}).Directory_separator, fn.count) + if p > 0 then + Result := fn.substring (1, p - 1) + else + create Result.make_empty + end + end + +feature -- Content-type related + + get_content_type + -- Content type associated with `file_name' + local + m_map: HTTP_FILE_EXTENSION_MIME_MAPPING + m: detachable READABLE_STRING_8 + do + create m_map.make_default + m := m_map.mime_type (file_extension (file_name).as_lower) + if m = Void then + m := {HTTP_MIME_TYPES}.application_force_download + end + content_type := m + end + +feature {NONE} -- Implementation: output + + send_file_content_to (fn: READABLE_STRING_8; res: WSF_RESPONSE) + -- Send the content of file `fn' + require + string_not_empty: not fn.is_empty + is_readable: (create {RAW_FILE}.make (fn)).is_readable + local + f: RAW_FILE + do + create f.make (fn) + check f.exists and then f.is_readable end + + f.open_read + from + until + f.exhausted + loop + f.read_stream (4_096) + res.write_string (f.last_string) + end + f.close + end + +end diff --git a/library/server/wsf/src/response/wsf_page_response.e b/library/server/wsf/src/response/wsf_page_response.e new file mode 100644 index 00000000..25773eef --- /dev/null +++ b/library/server/wsf/src/response/wsf_page_response.e @@ -0,0 +1,45 @@ +note + description: "Summary description for {WSF_PAGE_RESPONSE}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + WSF_PAGE_RESPONSE + +inherit + WSF_RESPONSE_MESSAGE + +create + make + +feature {NONE} -- Initialization + + make + do + status_code := {HTTP_STATUS_CODE}.ok + create header.make + end + +feature -- Status + + status_code: INTEGER + +feature -- Header + + header: HTTP_HEADER + + body: detachable STRING_8 + +feature -- Output + + send_to (res: WSF_RESPONSE) + do + res.set_status_code (status_code) + res.write_header_text (header.string) + if attached body as b then + res.write_string (b) + end + end + +end diff --git a/library/server/wsf/src/wsf_response.e b/library/server/wsf/src/wsf_response.e index a3dbbe0a..4f095437 100644 --- a/library/server/wsf/src/wsf_response.e +++ b/library/server/wsf/src/wsf_response.e @@ -8,6 +8,9 @@ note class WSF_RESPONSE +inherit + WGI_RESPONSE + create {WSF_SERVICE} make_from_wgi @@ -55,14 +58,8 @@ feature -- Status setting set_status_code (a_code: INTEGER) -- Set response status code -- Should be done before sending any data back to the client - require - status_not_set: not status_is_set - header_not_committed: not header_committed do wgi_response.set_status_code (a_code) - ensure - status_code_set: status_code = a_code - status_set: status_is_set end status_code: INTEGER @@ -71,18 +68,20 @@ feature -- Status setting Result := wgi_response.status_code end +feature {WGI_RESPONSE} -- Core output operation + + write (s: READABLE_STRING_8) + -- Send the string `s' + -- this can be used for header and body + do + wgi_response.write (s) + end + feature -- Header output operation write_header_text (a_headers: READABLE_STRING_8) - require - status_set: status_is_set - header_not_committed: not header_committed do wgi_response.write_header_text (a_headers) - ensure - status_set: status_is_set - header_committed: header_committed - message_writable: message_writable end write_header (a_status_code: INTEGER; a_headers: detachable ARRAY [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]]) @@ -114,20 +113,21 @@ feature -- Header output operation message_writable: message_writable end + write_header_lines (a_lines: ITERABLE [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) + do + wgi_response.write_header_lines (a_lines) + end + feature -- Output operation write_string (s: READABLE_STRING_8) -- Send the string `s' - require - message_writable: message_writable do wgi_response.write_string (s) end write_substring (s: READABLE_STRING_8; a_begin_index, a_end_index: INTEGER) -- Send the substring `s[a_begin_index:a_end_index]' - require - message_writable: message_writable do wgi_response.write_substring (s, a_begin_index, a_end_index) end @@ -163,14 +163,6 @@ feature -- Output operation flush end - write_file_content (fn: READABLE_STRING_8) - -- Send the content of file `fn' - require - message_writable: message_writable - do - wgi_response.write_file_content (fn) - end - flush -- Flush if it makes sense do @@ -179,6 +171,17 @@ feature -- Output operation feature -- Helper + put_response (obj: WSF_RESPONSE_MESSAGE) + require + not header_committed + not status_is_set + not message_committed + do + obj.send_to (Current) + end + +feature -- Redirect + redirect_now_with_custom_status_code (a_url: READABLE_STRING_8; a_status_code: INTEGER) -- Redirect to the given url `a_url' and precise custom `a_status_code' -- Please see http://www.faqs.org/rfcs/rfc2616 to use proper status code. @@ -205,6 +208,14 @@ feature -- Helper redirect_now_with_custom_status_code (a_url, {HTTP_STATUS_CODE}.moved_permanently) end +feature {WGI_RESPONSE, WGI_SERVICE} -- Commit + + commit + -- Commit the current response + do + wgi_response.commit + end + note copyright: "2011-2011, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/library/server/wsf/src/wsf_response_message.e b/library/server/wsf/src/wsf_response_message.e new file mode 100644 index 00000000..4e65eebf --- /dev/null +++ b/library/server/wsf/src/wsf_response_message.e @@ -0,0 +1,16 @@ +note + description: "Summary description for {WSF_RESPONSE_MESSAGE}." + author: "" + date: "$Date$" + revision: "$Revision$" + +deferred class + WSF_RESPONSE_MESSAGE + +feature -- Output + + send_to (res: WSF_RESPONSE) + deferred + end + +end