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
l_server : HTTP_SERVER
l_cfg: HTTP_SERVER_CONFIGURATION
l_http_handler : HTTP_CONNECTION_HANDLER
l_http_handler : HTTP_HANDLER
do
create l_cfg.make
l_cfg.http_server_port := 9_000
l_cfg.document_root := default_document_root
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)
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
feature -- Status Report
is_readable: BOOLEAN
-- Is readable?
do
Result := source.is_open_read
end
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 a string of at most `nb_char' bound characters
-- or until end of file.
-- Make result available in `last_string'.
require
nb_char_positive: nb_char > 0
is_readable: is_readable
do
last_string.wipe_out
if source.socket_ok then

View File

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

View File

@@ -4,16 +4,14 @@ note
date: "$Date$"
revision: "$Revision$"
class
deferred class
HTTP_CONNECTION_HANDLER
inherit
THREAD
HTTP_CONSTANTS
create
HTTP_HANDLER
redefine
make
end
feature {NONE} -- Initialization
@@ -22,70 +20,26 @@ feature {NONE} -- Initialization
--
-- `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
create current_request_message.make_empty
Precursor (a_main_server, a_name)
create method.make_empty
create uri.make_empty
create request_header.make_empty
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
feature -- Inherited Features
feature -- Execution
execute
-- <Precursor>
-- Creates a socket and connects to the http server.
receive_message_and_send_reply (client_socket: TCP_STREAM_SOCKET)
local
l_http_socket: detachable TCP_STREAM_SOCKET
l_http_port: INTEGER
l_input: HTTP_INPUT_STREAM
l_output: HTTP_OUTPUT_STREAM
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_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.")
create l_input.make (client_socket)
create l_output.make (client_socket)
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
analyze_request_message (l_input)
process_request (uri, method, request_header_map, request_header, l_input, l_output)
end
feature -- Request processing
@@ -98,70 +52,17 @@ feature -- Request processing
a_headers_text_attached: a_headers_text /= Void
a_input_attached: a_input /= Void
a_output_attached: a_output /= Void
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")
deferred
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
request_header: STRING
-- Header' source
request_header_map : HASH_TABLE [STRING,STRING]
-- 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
-- http verb
@@ -172,54 +73,7 @@ feature {NONE} -- Access
-- http_version
--| unused for now
feature -- Status setting
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
feature -- Parsing
parse_http_request_line (line: STRING)
require
@@ -237,63 +91,44 @@ feature -- New implementation
not_void_method: method /= Void
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
socket: socket /= Void and then not socket.is_closed
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
input_redable: a_input /= Void and then not a_input.is_readable
local
end_of_stream : BOOLEAN
pos : INTEGER
line : STRING
txt: STRING
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
socket.read_line_thread_aware
Result := ""
a_input.read_line
until
end_of_stream
loop
line := socket.last_string
line := a_input.last_string
print ("%N" +line+ "%N")
pos := line.index_of (':',1)
request_header_map.put (line.substring (pos + 1, line.count), line.substring (1,pos-1))
Result.append (line)
Result.append_character ('%N')
if not line.is_equal("%R") and socket.socket_ok then
socket.read_line_thread_aware
else
txt.append (line)
txt.append_character ('%N')
if line.is_empty or else line[1] = '%R' then
end_of_stream := True
else
a_input.read_line
end
end
end
parse_request_line_internal (line: STRING)
analyze_request_line (line: STRING)
require
line /= Void
local
@@ -310,7 +145,6 @@ feature -- New Implementation
end
invariant
main_server_attached: main_server /= Void
current_request_message_attached: current_request_message /= Void
request_header_attached: request_header /= Void
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
loop
if socket.socket_ok then
done := receive_message_and_send_replay (socket)
done := receive_message_and_send_reply (socket)
request_header_map.wipe_out
else
done := True
@@ -80,7 +80,7 @@ feature -- Implementation
io.put_new_line
end
receive_message_and_send_replay (client_socket: NETWORK_STREAM_SOCKET): BOOLEAN
receive_message_and_send_reply (client_socket: NETWORK_STREAM_SOCKET): BOOLEAN
require
socket_attached: client_socket /= Void
socket_valid: client_socket.is_open_read and then client_socket.is_open_write

View File

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