A few change to make it more customizable

and prepare integration to EiffelWebReloaded (see on github)
This commit is contained in:
Jocelyn Fiat
2011-05-26 17:23:21 +02:00
parent 85cf39f3c6
commit 64cf2b6936
23 changed files with 590 additions and 298 deletions

View File

@@ -0,0 +1,316 @@
note
description: "Summary description for {HTTP_CONNECTION_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
HTTP_CONNECTION_HANDLER
inherit
THREAD
HTTP_CONSTANTS
create
make
feature {NONE} -- Initialization
make (a_main_server: like main_server; a_name: STRING)
-- Creates a {HTTP_CONNECTION_HANDLER}, assigns the main_server and sets the current_request_message to empty.
--
-- `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
create method.make_empty
create uri.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
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_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
ll_http_socket.cleanup
check
socket_is_closed: ll_http_socket.is_closed
end
end
is_stop_requested := True
retry
end
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 ...
require
a_uri_attached: a_uri /= Void
a_method_attached: a_method /= Void
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")
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_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
uri: STRING
-- http endpoint
version: detachable STRING
-- 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
parse_http_request_line (line: STRING)
require
line /= Void
local
pos, next_pos: INTEGER
do
print ("%N parse http request line:%N" + line)
-- parse (this should be done by a lexer)
pos := line.index_of (' ', 1)
method := line.substring (1, pos - 1)
next_pos := line.index_of (' ', pos+1)
uri := line.substring (pos+1, next_pos-1)
ensure
not_void_method: method /= Void
end
feature -- New Implementation
receive_message_and_send_replay (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
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
local
end_of_stream : BOOLEAN
pos : INTEGER
line : STRING
do
from
socket.read_line_thread_aware
Result := ""
until
end_of_stream
loop
line := socket.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
end_of_stream := True
end
end
end
parse_request_line_internal (line: STRING)
require
line /= Void
local
pos, next_pos: INTEGER
do
print ("%N parse request line:%N" + line)
pos := line.index_of (' ', 1)
method := line.substring (1, pos - 1)
next_pos := line.index_of (' ', pos+1)
uri := line.substring (pos+1, next_pos-1)
version := line.substring (next_pos + 1, line.count)
ensure
not_void_method: method /= Void
end
invariant
main_server_attached: main_server /= Void
current_request_message_attached: current_request_message /= Void
end

146
server/http_constants.e Normal file
View File

@@ -0,0 +1,146 @@
note
description: "Summary description for {HTTP_CONSTANTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
HTTP_CONSTANTS
feature
http_version_1_1: STRING = "HTTP/1.1"
http_version_1_0: STRING = "HTTP/1.0"
crlf: STRING = "%/13/%/10/"
feature -- Status codes
-- 1xx Informational -Request received, continuing process
Continue : STRING = "100"
Switching_Protocols : STRING = "101"
-- 2xx Success - The action was successfully received, understood, and accepted
Ok: STRING = "200"
Created : STRING = "201"
Accepted : STRING = "202"
Non_Authoritative_Information : STRING = "203"
No_Content : STRING = "204"
Reset_Content : STRING = "205"
Parcial_Content : STRING = "206"
-- 3xx Redirection - Further Action must be taken in order to complete the request
Multiple_Choices : STRING = "300"
Moved_Permanently: STRING = "301"
Found : STRING = "302"
See_Other : STRING = "303"
Not_Modified : STRING = "304"
Use_Proxy : STRING = "305"
Temporary_Redirect : STRING = "307"
--4xx Client Error - The request contains bad syntax or cannot be fulfilled
Bad_Request : STRING = "400"
Unauthorized : STRING = "401"
Payment_Required : STRING = "402"
Forbidden : STRING = "403"
Not_Found : STRING = "404"
Method_Not_Allowed : STRING = "405"
Not_Acceptable : STRING = "406"
Proxy_Authentication_Required : STRING = "407"
Request_Time_out : STRING = "408"
Conflict : STRING = "409"
Gone : STRING = "410"
Length_Required : STRING = "411"
Precondition_Failed : STRING = "412"
Request_Entity_Too_Large : STRING = "413"
Request_URI_Too_Large : STRING = "414"
Unsupported_Media_Type : STRING = "415"
Requested_range_not_satisfiable : STRING = "416"
Expectation_Failed : STRING = "417"
--5xx Server Error - The server failed to fulfill an apparently valid request
server_error: STRING = "500"
Internal_Server_Error : STRING = "500"
Not_Implemented : STRING = "501"
Bad_Gateway : STRING = "502"
Service_Unavailable : STRING = "503"
Gateway_Time_out : STRING = "504"
HTTP_Version_not_supported : STRING = "505"
-- messages
ok_message: STRING = "OK"
continue_message : STRING = "Continue"
not_found_message: STRING = "URI not found"
not_implemented_message: STRING = "Not Implemented"
feature -- content types
text_html: STRING = "text/html"
feature -- General Header Fields
-- There are a few header fields which have general applicability for both request and response messages,
-- but which do not apply to the entity being transferred.
-- These header fields apply only to the message being transmitted.
Cache_control : STRING = "Cache-Control"
Connection : STRING = "Connection"
Date : STRING = "Date"
Pragma : STRING = "PRAGMA"
Trailer : STRING = "Trailer"
Transfer_encoding : STRING = "Transfer-Encoding"
Upgrade : STRING = "Upgrade"
Via : STRING = "Via"
Warning : STRING = "Warning"
feature -- Request Header
Accept : STRING = "Accept"
Accept_charset : STRING = "Accept-Charset"
Accept_encoding : STRING = "Accept-Encoding"
Accept_language : STRING = "Accept-Language"
Authorization : STRING = "Authorization"
Expect : STRING = "Expect"
From_header : STRING = "From"
Host : STRING = "Host"
If_match : STRING = "If-Match"
If_modified_since : STRING = "If-Modified-Since"
If_none_match : STRING = "If-None-Match"
If_range : STRING = "If-Range"
If_unmodified_since : STRING = "If-Unmodified-Since"
Max_forwards : STRING = "Max-Forwards"
Proxy_authorization : STRING = "Proxy-Authorization"
Range : STRING = "Range"
Referer : STRING = "Referrer"
TE : STRING = "TE"
User_agent : STRING = "User-Agent"
feature -- Entity Header
Allow : STRING = "Allow"
Content_encoding : STRING = "Content-Encoding"
Content_language : STRING = "Content-Language"
Content_length : STRING = "Content-Length"
Content_location : STRING = "Content-Location"
Content_MD5 : STRING = "Content-MD5"
Content_range : STRING = "Content-Range"
Content_type : STRING = "Content-Type"
Expires : STRING = "Expires"
Last_modified : STRING = "Last-Modified"
feature -- Http Method
Options : STRING = "OPTIONS"
Get : STRING = "GET"
Head : STRING = "HEAD"
Post : STRING = "POST"
Put : STRING = "PUT"
Delete : STRING = "DELETE"
Trace : STRING = "TRACE"
Connect : STRING = "CONNECT"
end

View File

@@ -0,0 +1,57 @@
note
description: "[
Provides features to encode and decode messages
]"
legal: "See notice at end of class."
status: "Community Preview 1.0"
date: "$Date: 2009-09-01 19:15:37 -0300 (mar 01 de sep de 2009) $"
revision: "$Revision: 80577 $"
class
HTTP_ENCODING_FACILITIES
create
make
feature -- Initialization
make
do
end
feature -- Conversion
encode_natural(a_i: NATURAL; a_is_fragmented: BOOLEAN): NATURAL
-- Leftshift of the natural (don't use numbers >= 2^31) and subsequent append of the flag bit.
-- Use decode_natural and decode_flag for decoding.
require
no_too_big: a_i < 2147483648
do
Result := (a_i |<< 1) + a_is_fragmented.to_integer.as_natural_32
end
change_flag(a_i: NATURAL; a_new_flag: BOOLEAN): NATURAL
-- Changes the flag to "new_flag" and doesn't change the encoded natural.
do
Result := (a_i & 0xFFFFFFFE) + a_new_flag.to_integer.as_natural_32
end
decode_natural_and_flag (a_i: NATURAL): TUPLE [NATURAL, BOOLEAN]
-- Convenience feature which combines both decodings (natural and flag)
do
Result := [decode_natural (a_i), decode_flag (a_i)]
end
decode_natural (a_i: NATURAL): NATURAL
-- The natural that was encoded in {ENCODING_FACILITIES}.encode_natural.
do
Result := (a_i |>> 1)
end
decode_flag (a_i: NATURAL): BOOLEAN
--`Result': the flag that was encoded in encode_natural
do
Result := (a_i.bit_and (1) = 1)
end
end

View File

@@ -0,0 +1,177 @@
class HTTP_PROTOCOL_HANDLER
inherit
SHARED_HTTP_REQUEST_HANDLERS
HTTP_CONSTANTS
create
make
feature -- Initialization
make (socket: NETWORK_STREAM_SOCKET)
require
valid_socket: socket /= Void and then socket.is_bound
local
done: BOOLEAN
client_socket: detachable NETWORK_STREAM_SOCKET
do
from
done := False
until
done
loop
socket.accept
client_socket := socket.accepted
if client_socket = Void then
-- Some error occured, perhaps because of the timeout
-- We probably should provide some diagnostics here
io.put_string ("accept result = Void")
io.put_new_line
else
perform_client_communication (client_socket)
end
end
end
feature -- Access
method : STRING
uri : STRING
version : STRING
request_header_map : HASH_TABLE [STRING,STRING]
-- Containts key value of the header
feature -- Implementation
perform_client_communication (socket: NETWORK_STREAM_SOCKET)
require
socket_attached: socket /= Void
socket_valid: socket.is_open_read and then socket.is_open_write
local
done: BOOLEAN
l_address, l_peer_address: detachable NETWORK_SOCKET_ADDRESS
do
l_address := socket.address
l_peer_address := socket.peer_address
check
l_address_attached: l_address /= Void
l_peer_address_attached: l_peer_address /= Void
end
io.put_string ("Accepted client on the listen socket address = "+ l_address.host_address.host_address + " port = " + l_address.port.out +".")
io.put_new_line
io.put_string ("%T Accepted client address = " + l_peer_address.host_address.host_address + " , port = " + l_peer_address.port.out)
io.put_new_line
create request_header_map.make (20)
from
done := False
until
done
loop
if socket.socket_ok then
done := receive_message_and_send_replay (socket)
request_header_map.wipe_out
else
done := True
end
end
io.put_string ("Finished processing the client, address = "+ l_peer_address.host_address.host_address + " port = " + l_peer_address.port.out + ".")
io.put_new_line
end
receive_message_and_send_replay (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
local
message: detachable STRING
l_http_request : HTTP_REQUEST_HANDLER
do
parse_request_line (client_socket)
message := receive_message_internal (client_socket)
if method.is_equal (Get) then
create {GET_REQUEST_HANDLER} l_http_request
l_http_request.set_uri (uri)
l_http_request.process
send_message (client_socket, l_http_request.answer.reply_header + l_http_request.answer.reply_text)
elseif method.is_equal (Post) then
elseif method.is_equal (Put) then
elseif method.is_equal (Options) then
elseif method.is_equal (Head) then
elseif method.is_equal (Delete) then
elseif method.is_equal (Trace) then
elseif method.is_equal (Connect) then
else
debug
print ("Method not supported")
end
end
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: NETWORK_STREAM_SOCKET) : STRING
require
socket: socket /= Void and then not socket.is_closed
local
end_of_stream : BOOLEAN
pos : INTEGER
line : STRING
do
from
socket.read_line
Result := ""
until
end_of_stream
loop
line := socket.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(socket.last_string)
if not socket.last_string.is_equal("%R") and socket.socket_ok then
socket.read_line
else
end_of_stream := True
end
end
end
send_message (client_socket : NETWORK_STREAM_SOCKET ; a_msg: STRING)
local
a_package : PACKET
a_data : MANAGED_POINTER
c_string : C_STRING
do
create c_string.make (a_msg)
create a_data.make_from_pointer (c_string.item, a_msg.count + 1)
create a_package.make_from_managed_pointer (a_data)
client_socket.send (a_package, 0)
end
parse_request_line_internal (line: STRING)
require
line /= Void
local
pos, next_pos: INTEGER
do
print ("%N parse request line:%N" + line)
pos := line.index_of (' ', 1)
method := line.substring (1, pos - 1)
next_pos := line.index_of (' ', pos+1)
uri := line.substring (pos+1, next_pos-1)
version := line.substring (next_pos + 1, line.count)
ensure
not_void_method: method /= Void
end
end

61
server/http_server.e Normal file
View File

@@ -0,0 +1,61 @@
note
description: "Summary description for {HTTP_SERVER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
HTTP_SERVER
inherit
SHARED_DOCUMENT_ROOT
create
make
feature -- Initialization
make (cfg: like configuration)
do
configuration := cfg
end
setup (a_http_handler : HTTP_CONNECTION_HANDLER)
require
a_http_handler_valid: a_http_handler /= Void
do
print("%N%N%N")
print ("Starting Web Application Server:%N")
stop_requested := False
set_server_configuration (configuration)
a_http_handler.launch
run
end
shutdown_server
do
stop_requested := True
end
feature -- Access
configuration: HTTP_SERVER_CONFIGURATION
-- Configuration of the server
stop_requested: BOOLEAN
-- Stops the server
feature {NONE} -- implementation
run
-- Start the server
local
e: EXECUTION_ENVIRONMENT
do
create e
from until stop_requested loop
e.sleep (1_000_000)
end
end
end

View File

@@ -0,0 +1,15 @@
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

View File

@@ -0,0 +1,13 @@
class SHARED_HTTP_REQUEST_HANDLERS
feature
http_request_handlers: HASH_TABLE [HTTP_REQUEST_HANDLER, STRING]
local
a_handler: HTTP_REQUEST_HANDLER
once
create Result.make (5)
create {GET_REQUEST_HANDLER} a_handler
Result.put (a_handler, "GET")
end
end

View File

@@ -0,0 +1,15 @@
note
description: "Summary description for {SHARED_URI_CONTENTS_TYPES}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
SHARED_URI_CONTENTS_TYPES
feature
ct_table: URI_CONTENTS_TYPES
once
create Result.make
end
end

View File

@@ -0,0 +1,32 @@
note
description: "Summary description for {TCP_STREAM_SOCKET}."
date: "$Date$"
revision: "$Revision$"
class
TCP_STREAM_SOCKET
inherit
NETWORK_STREAM_SOCKET
create
make_server_by_port
create {NETWORK_STREAM_SOCKET}
make_from_descriptor_and_address
feature -- Basic operation
send_message (a_msg: STRING)
local
a_package : PACKET
a_data : MANAGED_POINTER
c_string : C_STRING
do
create c_string.make (a_msg)
create a_data.make_from_pointer (c_string.item, a_msg.count + 1)
create a_package.make_from_managed_pointer (a_data)
send (a_package, 1)
end
end

View File

@@ -0,0 +1,88 @@
class URI_CONTENTS_TYPES
create
make
feature
content_types: HASH_TABLE [STRING, STRING]
extension (uri: STRING): STRING
-- extract extendion from a URI
local
i: INTEGER
do
-- going from the end find the position of the "."
from
i := uri.count
until
i = 0 or else uri.item (i) = '.'
loop
i := i - 1
end
Result := uri.substring (i+1, uri.count)
end
feature {NONE}
make
do
create content_types.make (30)
content_types.put ("text/html", "html")
content_types.put ("text/html", "htm")
content_types.put ("image/gif", "gif")
content_types.put ("image/jpeg", "jpeg")
content_types.put ("image/png", "jpg")
content_types.put ("image/png", "png")
end
feature -- Access: Encoding
urlencode (s: STRING): STRING
-- URL encode `s'
do
Result := s.string
Result.replace_substring_all ("#", "%%23")
Result.replace_substring_all (" ", "%%20")
Result.replace_substring_all ("%T", "%%09")
Result.replace_substring_all ("%N", "%%0A")
Result.replace_substring_all ("/", "%%2F")
Result.replace_substring_all ("&", "%%26")
Result.replace_substring_all ("<", "%%3C")
Result.replace_substring_all ("=", "%%3D")
Result.replace_substring_all (">", "%%3E")
Result.replace_substring_all ("%"", "%%22")
Result.replace_substring_all ("%'", "%%27")
end
urldecode (s: STRING): STRING
-- URL decode `s'
do
Result := s.string
Result.replace_substring_all ("%%23", "#")
Result.replace_substring_all ("%%20", " ")
Result.replace_substring_all ("%%09", "%T")
Result.replace_substring_all ("%%0A", "%N")
Result.replace_substring_all ("%%2F", "/")
Result.replace_substring_all ("%%26", "&")
Result.replace_substring_all ("%%3C", "<")
Result.replace_substring_all ("%%3D", "=")
Result.replace_substring_all ("%%3E", ">")
Result.replace_substring_all ("%%22", "%"")
Result.replace_substring_all ("%%27", "%'")
end
stripslashes (s: STRING): STRING
do
Result := s.string
Result.replace_substring_all ("\%"", "%"")
Result.replace_substring_all ("\'", "'")
Result.replace_substring_all ("\/", "/")
Result.replace_substring_all ("\\", "\")
end
end