abstracted the HTTP_HANDLER

to let the user integrate at the level of its choice
(either very early so handle itself the header handling, or later to reuse existing code)
This commit is contained in:
Jocelyn Fiat
2011-05-27 13:07:06 +02:00
parent 64cf2b6936
commit c553bd1e1e
10 changed files with 249 additions and 277 deletions

View File

@@ -19,14 +19,14 @@ feature {NONE} -- Initialization
local local
l_server : HTTP_SERVER l_server : HTTP_SERVER
l_cfg: HTTP_SERVER_CONFIGURATION l_cfg: HTTP_SERVER_CONFIGURATION
l_http_handler : HTTP_CONNECTION_HANDLER l_http_handler : HTTP_HANDLER
do do
create l_cfg.make create l_cfg.make
l_cfg.http_server_port := 9_000 l_cfg.http_server_port := 9_000
l_cfg.document_root := default_document_root l_cfg.document_root := default_document_root
create l_server.make (l_cfg) create l_server.make (l_cfg)
create l_http_handler.make (l_server, "HTTP_HANDLER") create {APPLICATION_CONNECTION_HANDLER} l_http_handler.make (l_server, "HTTP_HANDLER")
l_server.setup (l_http_handler) l_server.setup (l_http_handler)
end end

57
application_handler.e Normal file
View File

@@ -0,0 +1,57 @@
note
description: "Summary description for {HTTP_CONNECTION_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
APPLICATION_CONNECTION_HANDLER
inherit
HTTP_CONNECTION_HANDLER
create
make
feature -- Request processing
process_request (a_uri: STRING; a_method: STRING; a_headers_map: HASH_TABLE [STRING, STRING]; a_headers_text: STRING; a_input: HTTP_INPUT_STREAM; a_output: HTTP_OUTPUT_STREAM)
-- Process request ...
do
if a_method.is_equal (Get) then
execute_get_request (a_uri, a_headers_map, a_headers_text, a_input, a_output)
elseif a_method.is_equal (Post) then
execute_post_request (a_uri, a_headers_map, a_headers_text, a_input, a_output)
elseif a_method.is_equal (Put) then
elseif a_method.is_equal (Options) then
elseif a_method.is_equal (Head) then
elseif a_method.is_equal (Delete) then
elseif a_method.is_equal (Trace) then
elseif a_method.is_equal (Connect) then
else
debug
print ("Method [" + a_method + "] not supported")
end
end
end
execute_get_request (a_uri: STRING; a_headers_map: HASH_TABLE [STRING, STRING]; a_headers_text: STRING; a_input: HTTP_INPUT_STREAM; a_output: HTTP_OUTPUT_STREAM)
local
l_http_request : HTTP_REQUEST_HANDLER
do
create {GET_REQUEST_HANDLER} l_http_request.make (a_input, a_output)
l_http_request.set_uri (a_uri)
l_http_request.process
end
execute_post_request (a_uri: STRING; a_headers_map: HASH_TABLE [STRING, STRING]; a_headers_text: STRING; a_input: HTTP_INPUT_STREAM; a_output: HTTP_OUTPUT_STREAM)
local
l_http_request : HTTP_REQUEST_HANDLER
do
check not_yet_implemented: False end
create {POST_REQUEST_HANDLER} l_http_request.make (a_input, a_output)
l_http_request.set_uri (a_uri)
l_http_request.process
end
end

View File

@@ -1,47 +0,0 @@
nino: system execution failed.
Following is the set of recorded exceptions:
******************************** Thread exception *****************************
In thread Child thread 0x88 (thread id)
*******************************************************************************
-------------------------------------------------------------------------------
Class / Object Routine Nature of exception Effect
-------------------------------------------------------------------------------
TCP_STREAM_SOCKET send @1 socket_exists:
<00000000024B1A48> (From SOCKET) Precondition violated. Fail
-------------------------------------------------------------------------------
GET_REQUEST_HANDLER send_message @4
<00000000024B4428> Routine failure. Fail
-------------------------------------------------------------------------------
GET_REQUEST_HANDLER execute @2
<00000000024B4428> Routine failure. Fail
-------------------------------------------------------------------------------
GET_REQUEST_HANDLER thr_main @4
<00000000024B4428> (From THREAD) Routine failure. Rescue
-------------------------------------------------------------------------------
APPLICATION root's creation
<00000000024B05A8> Routine failure. Exit
-------------------------------------------------------------------------------
nino: system execution failed.
Following is the set of recorded exceptions:
******************************** Thread exception *****************************
In thread Child thread 0x88 (thread id)
*******************************************************************************
-------------------------------------------------------------------------------
Class / Object Routine Nature of exception Effect
-------------------------------------------------------------------------------
TCP_STREAM_SOCKET put_string @1 extendible:
<0000000002621A48> (From SOCKET) Precondition violated. Fail
-------------------------------------------------------------------------------
GET_REQUEST_HANDLER execute @2
<0000000002624408> Routine failure. Fail
-------------------------------------------------------------------------------
GET_REQUEST_HANDLER thr_main @4
<0000000002624408> (From THREAD) Routine failure. Rescue
-------------------------------------------------------------------------------
APPLICATION root's creation
<00000000026205A8> Routine failure. Exit
-------------------------------------------------------------------------------

View File

@@ -20,14 +20,34 @@ feature {NONE} -- Initialization
source: TCP_STREAM_SOCKET source: TCP_STREAM_SOCKET
feature -- Status Report
is_readable: BOOLEAN
-- Is readable?
do
Result := source.is_open_read
end
feature -- Basic operation feature -- Basic operation
read_line
require
is_readable: is_readable
do
last_string.wipe_out
if source.socket_ok then
source.read_line_thread_aware
last_string.append_string (source.last_string)
end
end
read_stream (nb_char: INTEGER) read_stream (nb_char: INTEGER)
-- Read a string of at most `nb_char' bound characters -- Read a string of at most `nb_char' bound characters
-- or until end of file. -- or until end of file.
-- Make result available in `last_string'. -- Make result available in `last_string'.
require require
nb_char_positive: nb_char > 0 nb_char_positive: nb_char > 0
is_readable: is_readable
do do
last_string.wipe_out last_string.wipe_out
if source.socket_ok then if source.socket_ok then

View File

@@ -4,7 +4,7 @@
inherit inherit
HTTP_REQUEST_HANDLER HTTP_REQUEST_HANDLER
SHARED_DOCUMENT_ROOT HTTP_SERVER_SHARED_CONFIGURATION
undefine undefine
default_create default_create
end end

View File

@@ -4,16 +4,14 @@ note
date: "$Date$" date: "$Date$"
revision: "$Revision$" revision: "$Revision$"
class deferred class
HTTP_CONNECTION_HANDLER HTTP_CONNECTION_HANDLER
inherit inherit
THREAD HTTP_HANDLER
redefine
HTTP_CONSTANTS make
end
create
make
feature {NONE} -- Initialization feature {NONE} -- Initialization
@@ -22,70 +20,26 @@ feature {NONE} -- Initialization
-- --
-- `a_main_server': The main server object -- `a_main_server': The main server object
-- `a_name': The name of this module -- `a_name': The name of this module
require
a_main_server_attached: a_main_server /= Void
a_name_attached: a_name /= Void
do do
main_server := a_main_server Precursor (a_main_server, a_name)
create current_request_message.make_empty
create method.make_empty create method.make_empty
create uri.make_empty create uri.make_empty
create request_header.make_empty
create request_header_map.make (10) create request_header_map.make (10)
is_stop_requested := False
ensure
main_server_set: a_main_server ~ main_server
current_request_message_attached: current_request_message /= Void
end end
feature -- Inherited Features feature -- Execution
execute receive_message_and_send_reply (client_socket: TCP_STREAM_SOCKET)
-- <Precursor>
-- Creates a socket and connects to the http server.
local local
l_http_socket: detachable TCP_STREAM_SOCKET l_input: HTTP_INPUT_STREAM
l_http_port: INTEGER l_output: HTTP_OUTPUT_STREAM
do do
is_stop_requested := False create l_input.make (client_socket)
l_http_port := main_server_configuration.http_server_port create l_output.make (client_socket)
create l_http_socket.make_server_by_port (l_http_port)
if not l_http_socket.is_bound then
print ("Socket could not be bound on port " + l_http_port.out )
else
from
l_http_socket.listen (main_server_configuration.max_tcp_clients)
print ("%NHTTP Connection Server ready on port " + l_http_port.out +"%N")
until
is_stop_requested
loop
l_http_socket.accept
if not is_stop_requested then
if attached l_http_socket.accepted as l_thread_http_socket then
receive_message_and_send_replay (l_thread_http_socket)
l_thread_http_socket.cleanup
check
socket_closed: l_thread_http_socket.is_closed
end
end
end
end
l_http_socket.cleanup
check
socket_is_closed: l_http_socket.is_closed
end
end
print ("HTTP Connection Server ends.")
rescue
print ("HTTP Connection Server shutdown due to exception. Please relaunch manually.")
if attached l_http_socket as ll_http_socket then analyze_request_message (l_input)
ll_http_socket.cleanup process_request (uri, method, request_header_map, request_header, l_input, l_output)
check
socket_is_closed: ll_http_socket.is_closed
end
end
is_stop_requested := True
retry
end end
feature -- Request processing feature -- Request processing
@@ -98,70 +52,17 @@ feature -- Request processing
a_headers_text_attached: a_headers_text /= Void a_headers_text_attached: a_headers_text /= Void
a_input_attached: a_input /= Void a_input_attached: a_input /= Void
a_output_attached: a_output /= Void a_output_attached: a_output /= Void
do deferred
if a_method.is_equal (Get) then
execute_get_request (a_uri, a_headers_map, a_headers_text, a_input, a_output)
elseif a_method.is_equal (Post) then
execute_post_request (a_uri, a_headers_map, a_headers_text, a_input, a_output)
elseif a_method.is_equal (Put) then
elseif a_method.is_equal (Options) then
elseif a_method.is_equal (Head) then
elseif a_method.is_equal (Delete) then
elseif a_method.is_equal (Trace) then
elseif a_method.is_equal (Connect) then
else
debug
print ("Method [" + a_method + "] not supported")
end
end
end end
execute_get_request (a_uri: STRING; a_headers_map: HASH_TABLE [STRING, STRING]; a_headers_text: STRING; a_input: HTTP_INPUT_STREAM; a_output: HTTP_OUTPUT_STREAM)
local
l_http_request : HTTP_REQUEST_HANDLER
do
create {GET_REQUEST_HANDLER} l_http_request.make (a_input, a_output)
l_http_request.set_uri (a_uri)
l_http_request.process
-- client_socket.put_string (l_http_request.answer.reply_header + l_http_request.answer.reply_text)
end
execute_post_request (a_uri: STRING; a_headers_map: HASH_TABLE [STRING, STRING]; a_headers_text: STRING; a_input: HTTP_INPUT_STREAM; a_output: HTTP_OUTPUT_STREAM)
local
l_http_request : HTTP_REQUEST_HANDLER
do
check not_yet_implemented: False end
create {POST_REQUEST_HANDLER} l_http_request.make (a_input, a_output)
l_http_request.set_uri (a_uri)
l_http_request.process
-- client_socket.put_string (l_http_request.answer.reply_header + l_http_request.answer.reply_text)
end
feature -- Access
is_stop_requested: BOOLEAN
-- Set true to stop accept loop
feature {NONE} -- Access feature {NONE} -- Access
request_header: STRING
-- Header' source
request_header_map : HASH_TABLE [STRING,STRING] request_header_map : HASH_TABLE [STRING,STRING]
-- Contains key:value of the header -- Contains key:value of the header
main_server: HTTP_SERVER
-- The main server object
main_server_configuration: HTTP_SERVER_CONFIGURATION
-- The main server's configuration
do
Result := main_server.configuration
end
current_request_message: STRING
-- Stores the current request message received from http server
Max_fragments: INTEGER = 1000
-- Defines the maximum number of fragments that can be received
method: STRING method: STRING
-- http verb -- http verb
@@ -172,54 +73,7 @@ feature {NONE} -- Access
-- http_version -- http_version
--| unused for now --| unused for now
feature -- Status setting feature -- Parsing
shutdown
-- Stops the thread
do
is_stop_requested := True
end
feature {NONE} -- Implementation
read_string_from_socket (a_socket: TCP_STREAM_SOCKET; a_n: NATURAL): STRING
-- Reads characters from the socket and concatenates them to a string
--
-- `a_socket': The socket to read from
-- `a_n': The number of characters to read
-- `Result': The created string
require
socket_is_open: not a_socket.is_closed
local
l_read_size: INTEGER
l_buf: detachable STRING
do
create Result.make (a_n.as_integer_32)
from
l_read_size := 0
Result := ""
l_buf := ""
until
l_buf.is_equal ("%R")
loop
a_socket.read_line_thread_aware
l_buf := a_socket.last_string
if l_buf /= Void then
Result.append (l_buf)
end
if l_buf.is_equal ("%R") then
a_socket.set_nodelay
a_socket.put_string ("HTTP/1.1 100 Continue%/13/%/10/%/13/%/10/")
a_socket.close_socket
end
l_read_size := Result.count
end
ensure
Result_attached: Result /= Void
end
feature -- New implementation
parse_http_request_line (line: STRING) parse_http_request_line (line: STRING)
require require
@@ -237,63 +91,44 @@ feature -- New implementation
not_void_method: method /= Void not_void_method: method /= Void
end end
feature -- New Implementation
receive_message_and_send_replay (client_socket: TCP_STREAM_SOCKET) analyze_request_message (a_input: HTTP_INPUT_STREAM)
require
socket_attached: client_socket /= Void
-- socket_valid: client_socket.is_open_read and then client_socket.is_open_write
a_http_socket:client_socket /= Void and then not client_socket.is_closed
local
l_headers_text: detachable STRING
l_input: HTTP_INPUT_STREAM
l_output: HTTP_OUTPUT_STREAM
do
parse_request_line (client_socket)
l_headers_text := receive_message_internal (client_socket)
create l_input.make (client_socket)
create l_output.make (client_socket)
process_request (uri, method, request_header_map, l_headers_text, l_input, l_output)
end
parse_request_line (socket: NETWORK_STREAM_SOCKET)
require require
socket: socket /= Void and then not socket.is_closed input_redable: a_input /= Void and then not a_input.is_readable
do
socket.read_line
parse_request_line_internal (socket.last_string)
end
receive_message_internal (socket: TCP_STREAM_SOCKET) : STRING
require
socket: socket /= Void and then not socket.is_closed
local local
end_of_stream : BOOLEAN end_of_stream : BOOLEAN
pos : INTEGER pos : INTEGER
line : STRING line : STRING
txt: STRING
do do
create txt.make (64)
a_input.read_line
line := a_input.last_string
analyze_request_line (line)
txt.append (line)
txt.append_character ('%N')
request_header := txt
from from
socket.read_line_thread_aware a_input.read_line
Result := ""
until until
end_of_stream end_of_stream
loop loop
line := socket.last_string line := a_input.last_string
print ("%N" +line+ "%N") print ("%N" +line+ "%N")
pos := line.index_of (':',1) pos := line.index_of (':',1)
request_header_map.put (line.substring (pos + 1, line.count), line.substring (1,pos-1)) request_header_map.put (line.substring (pos + 1, line.count), line.substring (1,pos-1))
Result.append (line) txt.append (line)
Result.append_character ('%N') txt.append_character ('%N')
if not line.is_equal("%R") and socket.socket_ok then if line.is_empty or else line[1] = '%R' then
socket.read_line_thread_aware
else
end_of_stream := True end_of_stream := True
end else
a_input.read_line
end
end end
end end
parse_request_line_internal (line: STRING) analyze_request_line (line: STRING)
require require
line /= Void line /= Void
local local
@@ -310,7 +145,6 @@ feature -- New Implementation
end end
invariant invariant
main_server_attached: main_server /= Void request_header_attached: request_header /= Void
current_request_message_attached: current_request_message /= Void
end end

123
server/http_handler.e Normal file
View File

@@ -0,0 +1,123 @@
note
description: "Summary description for {HTTP_CONNECTION_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
HTTP_HANDLER
inherit
THREAD
HTTP_CONSTANTS
feature {NONE} -- Initialization
make (a_main_server: like main_server; a_name: STRING)
-- Creates a {HTTP_HANDLER}, assigns the main_server and initialize various values
--
-- `a_main_server': The main server object
-- `a_name': The name of this module
require
a_main_server_attached: a_main_server /= Void
a_name_attached: a_name /= Void
do
main_server := a_main_server
is_stop_requested := False
ensure
main_server_set: a_main_server ~ main_server
end
feature -- Inherited Features
execute
-- <Precursor>
-- Creates a socket and connects to the http server.
local
l_http_socket: detachable TCP_STREAM_SOCKET
l_http_port: INTEGER
do
is_stop_requested := False
l_http_port := main_server_configuration.http_server_port
create l_http_socket.make_server_by_port (l_http_port)
if not l_http_socket.is_bound then
print ("Socket could not be bound on port " + l_http_port.out )
else
from
l_http_socket.listen (main_server_configuration.max_tcp_clients)
print ("%NHTTP Connection Server ready on port " + l_http_port.out +"%N")
until
is_stop_requested
loop
l_http_socket.accept
if not is_stop_requested then
if attached l_http_socket.accepted as l_thread_http_socket then
receive_message_and_send_reply (l_thread_http_socket)
l_thread_http_socket.cleanup
check
socket_closed: l_thread_http_socket.is_closed
end
end
end
end
l_http_socket.cleanup
check
socket_is_closed: l_http_socket.is_closed
end
end
print ("HTTP Connection Server ends.")
rescue
print ("HTTP Connection Server shutdown due to exception. Please relaunch manually.")
if attached l_http_socket as ll_http_socket then
ll_http_socket.cleanup
check
socket_is_closed: ll_http_socket.is_closed
end
end
is_stop_requested := True
retry
end
feature -- Access
is_stop_requested: BOOLEAN
-- Set true to stop accept loop
feature {NONE} -- Access
main_server: HTTP_SERVER
-- The main server object
main_server_configuration: HTTP_SERVER_CONFIGURATION
-- The main server's configuration
do
Result := main_server.configuration
end
Max_fragments: INTEGER = 1000
-- Defines the maximum number of fragments that can be received
feature -- Status setting
shutdown
-- Stops the thread
do
is_stop_requested := True
end
feature -- Execution
receive_message_and_send_reply (client_socket: TCP_STREAM_SOCKET)
require
socket_attached: client_socket /= Void
-- socket_valid: client_socket.is_open_read and then client_socket.is_open_write
a_http_socket:client_socket /= Void and then not client_socket.is_closed
deferred
end
invariant
main_server_attached: main_server /= Void
end

View File

@@ -70,7 +70,7 @@ feature -- Implementation
done done
loop loop
if socket.socket_ok then if socket.socket_ok then
done := receive_message_and_send_replay (socket) done := receive_message_and_send_reply (socket)
request_header_map.wipe_out request_header_map.wipe_out
else else
done := True done := True
@@ -80,7 +80,7 @@ feature -- Implementation
io.put_new_line io.put_new_line
end end
receive_message_and_send_replay (client_socket: NETWORK_STREAM_SOCKET): BOOLEAN receive_message_and_send_reply (client_socket: NETWORK_STREAM_SOCKET): BOOLEAN
require require
socket_attached: client_socket /= Void socket_attached: client_socket /= Void
socket_valid: client_socket.is_open_read and then client_socket.is_open_write socket_valid: client_socket.is_open_read and then client_socket.is_open_write

View File

@@ -8,7 +8,7 @@ class
HTTP_SERVER HTTP_SERVER
inherit inherit
SHARED_DOCUMENT_ROOT HTTP_SERVER_SHARED_CONFIGURATION
create create
make make
@@ -20,7 +20,7 @@ feature -- Initialization
configuration := cfg configuration := cfg
end end
setup (a_http_handler : HTTP_CONNECTION_HANDLER) setup (a_http_handler : HTTP_HANDLER)
require require
a_http_handler_valid: a_http_handler /= Void a_http_handler_valid: a_http_handler /= Void
do do
@@ -29,7 +29,7 @@ feature -- Initialization
stop_requested := False stop_requested := False
set_server_configuration (configuration) set_server_configuration (configuration)
a_http_handler.launch a_http_handler.launch
run a_http_handler.join
end end
shutdown_server shutdown_server

View File

@@ -1,15 +0,0 @@
note
description: "Summary description for {SHARED_DOCUMENT_ROOT}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
SHARED_DOCUMENT_ROOT
obsolete "Use HTTP_SERVER_SHARED_CONFIGURATION"
inherit
HTTP_SERVER_SHARED_CONFIGURATION
end