-
Notifications
You must be signed in to change notification settings - Fork 1
/
http_tcp_server.ml
172 lines (151 loc) · 5.79 KB
/
http_tcp_server.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
(*
OCaml HTTP - do it yourself (fully OCaml) HTTP daemon
Copyright (C) <2002-2005> Stefano Zacchiroli <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Library General Public License as
published by the Free Software Foundation, version 2.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*)
(** raised when a client timeouts *)
exception Timeout
let backlog = 10
(** if timeout is given (Some _) @return a new callback which establish
timeout_callback as callback for signal Sys.sigalrm and register an alarm
(expiring after timeout seconds) before invoking the real callback given. If
timeout is None, callback is returned unchanged. *)
let wrap_callback_w_timeout ~callback ~timeout ~timeout_callback =
match timeout with
| None -> callback
| Some timeout -> (* wrap callback setting an handler for ALRM signal and an
alarm that ring after timeout seconds *)
(fun inchan outchan ->
ignore (Sys.signal Sys.sigalrm (Sys.Signal_handle timeout_callback));
ignore (Unix.alarm timeout);
callback inchan outchan)
(* try to close nicely a socket *)
let shutdown_socket suck =
try
Unix.shutdown suck Unix.SHUTDOWN_ALL
with Unix.Unix_error(_, "shutdown", "") -> ()
let nice_unix_accept suck =
try
Unix.accept suck
with e -> (* clean up socket before exit *)
shutdown_socket suck;
raise e
let init_socket sockaddr =
let suck = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
(* shutdown socket on SIGTERM *)
ignore (Sys.signal Sys.sigterm
(Sys.Signal_handle
(fun _ -> shutdown_socket suck; exit 17)));
Unix.setsockopt suck Unix.SO_REUSEADDR true;
Unix.bind suck sockaddr;
Unix.listen suck backlog;
suck
let init_callback callback timeout =
let timeout_callback signo =
if signo = Sys.sigalrm then
raise Timeout
in
wrap_callback_w_timeout ~callback ~timeout ~timeout_callback
(** try to close an outchannel connected to a socket, ignore Sys_error since
* this probably means that socket is already closed (e.g. on sigpipe) *)
let try_close_out ch = try close_out ch with Sys_error _ -> ()
(** like Unix.establish_server, but shutdown sockets when receiving SIGTERM
and before exiting for an uncaught exception *)
let my_establish_server server_fun sockaddr =
let suck = init_socket sockaddr in
while true do
let (s, caller) = nice_unix_accept suck in
(** "double fork" trick, see {!Unix.establish_server} implementation *)
match Unix.fork() with
| 0 -> (* parent *)
(try
if Unix.fork () <> 0 then
exit 0; (* The son exits, the grandson works *)
let inchan = Unix.in_channel_of_descr s in
let outchan = Unix.out_channel_of_descr s in
server_fun inchan outchan;
try_close_out outchan; (* closes also inchan: socket is the same *)
exit 0
with e ->
shutdown_socket suck; (* clean up socket before exit *)
raise e)
| child when (child > 0) -> (* child *)
Unix.close s;
ignore (Unix.waitpid [] child) (* Reclaim the son *)
| _ (* < 0 *) ->
failwith "Can't fork"
done
(** tcp_server which forks a new process for each request *)
let fork ~sockaddr ~timeout callback =
let timeout_callback signo =
if signo = Sys.sigalrm then
exit 2
in
my_establish_server
(wrap_callback_w_timeout ~callback ~timeout ~timeout_callback)
sockaddr
(** tcp_server which doesn't fork, requests are server sequentially and in the
same address space of the calling process *)
let simple ~sockaddr ~timeout callback =
let suck = init_socket sockaddr in
let callback = init_callback callback timeout in
try
while true do
let (client, _) = Unix.accept suck in
(* client is now connected *)
let (inchan, outchan) =
(Unix.in_channel_of_descr client, Unix.out_channel_of_descr client)
in
(try
callback inchan outchan;
ignore (Unix.alarm 0) (* reset alarm *)
with Timeout -> ());
try_close_out outchan (* this close also inchan: socket is the same *)
done
with e -> (* clean up socket before exit *)
shutdown_socket suck;
raise e
(** tcp_server which creates a new thread for each request to be served *)
let thread ~sockaddr ~timeout callback =
let suck = init_socket sockaddr in
let callback = init_callback callback timeout in
let callback (i, o) =
(try
callback i o
with
| Timeout -> ()
| e ->
try_close_out o;
raise e);
try_close_out o
in
while true do
let (client, _) = nice_unix_accept suck in
(* client is now connected *)
let (inchan, outchan) =
(Unix.in_channel_of_descr client, Unix.out_channel_of_descr client)
in
Http_threaded_tcp_server.serve callback (inchan, outchan)
done
(** @param server an Http_types.tcp_server
* @return an Http_types.tcp_server which takes care of ignoring SIGPIPE during
* server execution and restoring previous handler when (if ever) the server
* returns *)
let handle_sigpipe server =
fun ~sockaddr ~timeout callback ->
let old_sigpipe_behavior = Sys.signal Sys.sigpipe Sys.Signal_ignore in
server ~sockaddr ~timeout callback;
ignore (Sys.signal Sys.sigpipe old_sigpipe_behavior)
let simple = handle_sigpipe simple
let thread = handle_sigpipe thread
let fork = handle_sigpipe fork