-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
server.cr
200 lines (181 loc) · 5.43 KB
/
server.cr
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
{% if !flag?(:without_openssl) %}
require "openssl"
{% end %}
require "socket"
require "./server/context"
require "./server/handler"
require "./server/response"
require "./common"
# An HTTP server.
#
# A server is given a handler that receives an `HTTP::Server::Context` that holds
# the `HTTP::Request` to process and must in turn configure and write to an `HTTP::Server::Response`.
#
# The `HTTP::Server::Response` object has `status` and `headers` properties that can be
# configured before writing the response body. Once response output is written,
# changing the `status` and `headers` properties has no effect.
#
# The `HTTP::Server::Response` is also a write-only `IO`, so all `IO` methods are available
# in it.
#
# The handler given to a server can simply be a block that receives an `HTTP::Server::Context`,
# or it can be an `HTTP::Handler`. An `HTTP::Handler` has an optional `next` handler,
# so handlers can be chained. For example, an initial handler may handle exceptions
# in a subsequent handler and return a 500 status code (see `HTTP::ErrorHandler`),
# the next handler might log the incoming request (see `HTTP::LogHandler`), and
# the final handler deals with routing and application logic.
#
# ### Simple Setup
#
# A handler is given with a block.
#
# ```
# require "http/server"
#
# server = HTTP::Server.new(8080) do |context|
# context.response.content_type = "text/plain"
# context.response.print "Hello world!"
# end
#
# puts "Listening on http://127.0.0.1:8080"
# server.listen
# ```
#
# ### With non-localhost bind address
#
# ```
# require "http/server"
#
# server = HTTP::Server.new("0.0.0.0", 8080) do |context|
# context.response.content_type = "text/plain"
# context.response.print "Hello world!"
# end
#
# puts "Listening on http://0.0.0.0:8080"
# server.listen
# ```
#
# ### Add handlers
#
# A series of handlers are chained.
#
# ```
# require "http/server"
#
# HTTP::Server.new("127.0.0.1", 8080, [
# HTTP::ErrorHandler.new,
# HTTP::LogHandler.new,
# HTTP::CompressHandler.new,
# HTTP::StaticFileHandler.new("."),
# ]).listen
# ```
#
# ### Add handlers and block
#
# A series of handlers is chained, the last one being the given block.
#
# ```
# require "http/server"
#
# server = HTTP::Server.new("0.0.0.0", 8080,
# [
# HTTP::ErrorHandler.new,
# HTTP::LogHandler.new,
# ]) do |context|
# context.response.content_type = "text/plain"
# context.response.print "Hello world!"
# end
#
# server.listen
# ```
class HTTP::Server
{% if !flag?(:without_openssl) %}
property tls : OpenSSL::SSL::Context::Server?
{% end %}
@wants_close = false
def self.new(port, &handler : Context ->)
new("127.0.0.1", port, &handler)
end
def self.new(port, handlers : Array(HTTP::Handler), &handler : Context ->)
new("127.0.0.1", port, handlers, &handler)
end
def self.new(port, handlers : Array(HTTP::Handler))
new("127.0.0.1", port, handlers)
end
def self.new(port, handler)
new("127.0.0.1", port, handler)
end
def initialize(@host : String, @port : Int32, &handler : Context ->)
@processor = RequestProcessor.new(handler)
end
def initialize(@host : String, @port : Int32, handlers : Array(HTTP::Handler), &handler : Context ->)
handler = HTTP::Server.build_middleware handlers, handler
@processor = RequestProcessor.new(handler)
end
def initialize(@host : String, @port : Int32, handlers : Array(HTTP::Handler))
handler = HTTP::Server.build_middleware handlers
@processor = RequestProcessor.new(handler)
end
def initialize(@host : String, @port : Int32, handler : HTTP::Handler | HTTP::Handler::Proc)
@processor = RequestProcessor.new(handler)
end
# Returns the TCP port the server is connected to.
#
# For example you may let the system choose a port, then report it:
# ```
# server = HTTP::Server.new(0) { }
# server.bind
# server.port # => 12345
# ```
def port
if server = @server
server.local_address.port.to_i
else
@port
end
end
# Creates the underlying `TCPServer` if the doesn't already exist.
#
# You may set *reuse_port* to true to enable the `SO_REUSEPORT` socket option,
# which allows multiple processes to bind to the same port.
def bind(reuse_port = false)
@server ||= TCPServer.new(@host, @port, reuse_port: reuse_port)
end
# Starts the server. Blocks until the server is closed.
#
# See `#bind` for details on the *reuse_port* argument.
def listen(reuse_port = false)
server = bind(reuse_port)
until @wants_close
spawn handle_client(server.accept?)
end
end
# Gracefully terminates the server. It will process currently accepted
# requests, but it won't accept new connections.
def close
@wants_close = true
@processor.close
if server = @server
server.close
@server = nil
end
end
private def handle_client(io)
# nil means the server was closed
return unless io
io.sync = false
{% if !flag?(:without_openssl) %}
if tls = @tls
io = OpenSSL::SSL::Socket::Server.new(io, tls, sync_close: true)
end
{% end %}
@processor.process(io, io)
end
# Builds all handlers as the middleware for `HTTP::Server`.
def self.build_middleware(handlers, last_handler : (Context ->)? = nil)
raise ArgumentError.new "You must specify at least one HTTP Handler." if handlers.empty?
0.upto(handlers.size - 2) { |i| handlers[i].next = handlers[i + 1] }
handlers.last.next = last_handler if last_handler
handlers.first
end
end