HTTP::Server - Crystal 1.4.1

# class HTTP::Server

- HTTP::Server
- Reference
- Object

##
Overview

A concurrent HTTP server implementation.

A server is initialized with a handler chain responsible for processing each incoming request.

    require "http/server"

    server = HTTP::Server.new do |context|
      context.response.content_type = "text/plain"
      context.response.print "Hello world!"
    end

    address = server.bind_tcp 8080
    puts "Listening on http://#{address}"
    server.listen

##
Request processing

The handler chain receives an instance of `HTTP::Server::Context` that holds the `HTTP::Request` to process and a `HTTP::Server::Response` which it can configure and write to.

Each connection is processed concurrently in a separate `Fiber` and can handle multiple subsequent requests-response cycles with connection keep-alive.

###
Handler chain

The handler given to a server can simply be a block that receives an `HTTP::Server::Context`, or it can be an instance of `HTTP::Handler`. An `HTTP::Handler` has a `#next` method to forward processing to the next handler in the chain.

For example, an initial handler might handle exceptions raised from subsequent handlers and return a `500 Server Error` status (see `HTTP::ErrorHandler`). The next handler might log all incoming requests (see `HTTP::LogHandler`). And the final handler deals with routing and application logic.

    require "http/server"

    server = HTTP::Server.new([
      HTTP::ErrorHandler.new,
      HTTP::LogHandler.new,
      HTTP::CompressHandler.new,
      HTTP::StaticFileHandler.new("."),
    ])

    server.bind_tcp "127.0.0.1", 8080
    server.listen

###
Response object

The `HTTP::Server::Response` object has `status` and `headers` properties that can be configured before writing the response body. Once any response output has been written, changing the `status` and `headers` properties has no effect.

The `HTTP::Server::Response` is a write-only `IO`, so all `IO` methods are available on it for sending the response body.

##
Binding to sockets

The server can be bound to one or more server sockets (see `#bind`)

Supported types:

- TCP socket: `#bind_tcp`, `#bind_unused_port`
- TCP socket with TLS/SSL: `#bind_tls`
- Unix socket `#bind_unix`

`#bind(uri : URI)` and `#bind(uri : String)` parse socket configuration for one of these types from an `URI`. This can be useful for injecting plain text configuration values.

Each of these methods returns the `Socket::Address` that was added to this server.

    require "http/server"

    server = HTTP::Server.new do |context|
      context.response.content_type = "text/plain"
      context.response.print "Hello world!"
    end

    address = server.bind_tcp "0.0.0.0", 8080
    puts "Listening on http://#{address}"
    server.listen

It is also possible to bind a generic `Socket::Server` using `#bind(socket : Socket::Server)` which can be used for custom network protocol configurations.

##
Server loop

After defining all server sockets to listen to, the server can be started by calling `#listen`. This call blocks until the server is closed.

A server can be closed by calling `#close`. This closes the server sockets and stops processing any new requests, even on connections with keep-alive enabled. Currently processing requests are not interrupted but also not waited for. In order to give them some grace period for finishing, the calling context can add a timeout like `sleep 10.seconds` after `#listen` returns.

###
Reusing connections

The request processor supports reusing a connection for subsequent requests. This is used by default for HTTP/1.1 or when requested by the `Connection: keep-alive` header. This is signalled by this header being set on the `HTTP::Server::Response` when it's passed into the handler chain.

If in the handler chain this header is overridden to `Connection: close`, then the connection will not be reused after the request has been processed.

Reusing the connection also requires that the request body (if present) is entirely consumed in the handler chain. Otherwise the connection will be closed.

##
Defined in:

http/server.cr
http/server/context.cr
http/server/response.cr

##
Constant Summary

Log = `::Log.for("http.server")`

##
Constructors

- .new(handlers : Array(HTTP::Handler), &handler : HTTP::Handler::HandlerProc) : self

  Creates a new HTTP server with a handler chain constructed from the handlers array and the given block.

- .new(&handler : HTTP::Handler::HandlerProc) : self

  Creates a new HTTP server with the given block as handler.

- .new(handlers : Array(HTTP::Handler)) : self

  Creates a new HTTP server with the handlers array as handler chain.

- .new(handler : HTTP::Handler | HTTP::Handler::HandlerProc)

  Creates a new HTTP server with the given handler.

##
Class Method Summary

- .build_middleware(handlers, last_handler : Context -> ? = nil)

  Builds all handlers as the middleware for `HTTP::Server`.

##
Instance Method Summary

- #addresses : Array(Socket::Address)
- #bind(uri : String) : Socket::Address

  Parses a socket configuration from uri and adds it to this server.

- #bind(uri : URI) : Socket::Address

  Parses a socket configuration from uri and adds it to this server.

- #bind(socket : Socket::Server) : Nil

  Adds a `Socket::Server` socket to this server.

- #bind_tcp(host : String, port : Int32, reuse_port : Bool = false) : Socket::IPAddress

  Creates a `TCPServer` listening on `host:port` and adds it as a socket, returning the local address and port the server listens on.

- #bind_tcp(port : Int32, reuse_port : Bool = false) : Socket::IPAddress

  Creates a `TCPServer` listening on `127.0.0.1:port` and adds it as a socket, returning the local address and port the server listens on.

- #bind_tcp(address : Socket::IPAddress, reuse_port : Bool = false) : Socket::IPAddress

  Creates a `TCPServer` listening on address and adds it as a socket, returning the local address and port the server listens on.

- #bind_tls(host : String, port : Int32, context : OpenSSL::SSL::Context::Server, reuse_port : Bool = false) : Socket::IPAddress

  Creates an `OpenSSL::SSL::Server` and adds it as a socket.

- #bind_tls(host : String, context : OpenSSL::SSL::Context::Server) : Socket::IPAddress

  Creates an `OpenSSL::SSL::Server` and adds it as a socket.

- #bind_tls(address : Socket::IPAddress, context : OpenSSL::SSL::Context::Server) : Socket::IPAddress

  Creates an `OpenSSL::SSL::Server` and adds it as a socket.

- #bind_unix(path : String) : Socket::UNIXAddress

  Creates a `UNIXServer` bound to path and adds it as a socket.

- #bind_unix(address : Socket::UNIXAddress) : Socket::UNIXAddress

  Creates a `UNIXServer` bound to address and adds it as a socket.

- #bind_unused_port(host : String = Socket::IPAddress::LOOPBACK, reuse_port : Bool = false) : Socket::IPAddress

  Creates a `TCPServer` listening on an unused port and adds it as a socket.

- #close : Nil

  Gracefully terminates the server.

- #closed? : Bool

  Returns `true` if this server is closed.

- #each_address(&block : Socket::Address -> )

  Enumerates all addresses this server is bound to.

- #listen(port : Int32, reuse_port : Bool = false)

  Creates a `TCPServer` listening on `127.0.0.1:port`, adds it as a socket and starts the server.

- #listen(host : String, port : Int32, reuse_port : Bool = false)

  Creates a `TCPServer` listening on `host:port`, adds it as a socket and starts the server.

- #listen : Nil

  Starts the server.

- #listening? : Bool

  Returns `true` if this server is listening on its sockets.

- #max_headers_size : Int32

  Returns the maximum permitted combined size for the headers in an HTTP request.

- #max_headers_size=(size : Int32)

  Sets the maximum permitted combined size for the headers in an HTTP request.

- #max_request_line_size : Int32

  Returns the maximum permitted size for the request line in an HTTP request.

- #max_request_line_size=(size : Int32)

  Sets the maximum permitted size for the request line in an HTTP request.

### Instance methods inherited from class `Reference`

==(other : self)
==(other : JSON::Any)
==(other : YAML::Any)
==(other) ==, dup dup, hash(hasher) hash, initialize initialize, inspect(io : IO) : Nil inspect, object_id : UInt64 object_id, pretty_print(pp) : Nil pretty_print, same?(other : Reference) : Bool
same?(other : Nil) same?, to_s(io : IO) : Nil to_s

### Constructor methods inherited from class `Reference`

new new

### Instance methods inherited from class `Object`

! : Bool !, !=(other) !=, !~(other) !~, ==(other) ==, ===(other : JSON::Any)
===(other : YAML::Any)
===(other) ===, =~(other) =~, as(type : Class) as, as?(type : Class) as?, class class, dup dup, hash(hasher)
hash hash, in?(collection : Object) : Bool
in?(*values : Object) : Bool in?, inspect(io : IO) : Nil
inspect : String inspect, is_a?(type : Class) : Bool is_a?, itself itself, nil? : Bool nil?, not_nil! not_nil!, pretty_inspect(width = 79, newline = "\n", indent = 0) : String pretty_inspect, pretty_print(pp : PrettyPrint) : Nil pretty_print, responds_to?(name : Symbol) : Bool responds_to?, tap(&) tap, to_json(io : IO) : Nil
to_json : String to_json, to_pretty_json(indent : String = " ") : String
to_pretty_json(io : IO, indent : String = " ") : Nil to_pretty_json, to_s(io : IO) : Nil
to_s : String to_s, to_yaml(io : IO) : Nil
to_yaml : String to_yaml, try(&) try, unsafe_as(type : T.class) forall T unsafe_as

### Class methods inherited from class `Object`

from_json(string_or_io, root : String)
from_json(string_or_io) from_json, from_yaml(string_or_io : String | IO) from_yaml

##
Constructor Detail

def self.new(handlers : Array(HTTP::Handler), &handler : HTTP::Handler::HandlerProc) : self #

Creates a new HTTP server with a handler chain constructed from the handlers array and the given block.

def self.new(&handler : HTTP::Handler::HandlerProc) : self #

Creates a new HTTP server with the given block as handler.

def self.new(handlers : Array(HTTP::Handler)) : self #

Creates a new HTTP server with the handlers array as handler chain.

def self.new(handler : HTTP::Handler | HTTP::Handler::HandlerProc) #

Creates a new HTTP server with the given handler.

##
Class Method Detail

def self.build_middleware(handlers, last_handler : Context -> ? = nil) #

Builds all handlers as the middleware for `HTTP::Server`.

##
Instance Method Detail

def addresses : Array(Socket::Address) #
def bind(uri : String) : Socket::Address #

Parses a socket configuration from uri and adds it to this server. Returns the effective address it is bound to.

    require "http/server"

    server = HTTP::Server.new { }
    server.bind("tcp://localhost:80")                                                  # => Socket::IPAddress.new("127.0.0.1", 8080)
    server.bind("unix:///tmp/server.sock")                                             # => Socket::UNIXAddress.new("/tmp/server.sock")
    server.bind("tls://127.0.0.1:443?key=private.key&cert=certificate.cert&ca=ca.crt") # => Socket::IPAddress.new("127.0.0.1", 443)

def bind(uri : URI) : Socket::Address #

Parses a socket configuration from uri and adds it to this server. Returns the effective address it is bound to.

    require "http/server"

    server = HTTP::Server.new { }
    server.bind("tcp://localhost:80")                                                  # => Socket::IPAddress.new("127.0.0.1", 8080)
    server.bind("unix:///tmp/server.sock")                                             # => Socket::UNIXAddress.new("/tmp/server.sock")
    server.bind("tls://127.0.0.1:443?key=private.key&cert=certificate.cert&ca=ca.crt") # => Socket::IPAddress.new("127.0.0.1", 443)

def bind(socket : Socket::Server) : Nil #

Adds a `Socket::Server` socket to this server.

def bind_tcp(host : String, port : Int32, reuse_port : Bool = false) : Socket::IPAddress #

Creates a `TCPServer` listening on `host:port` and adds it as a socket, returning the local address and port the server listens on.

    require "http/server"

    server = HTTP::Server.new { }
    server.bind_tcp("127.0.0.100", 8080) # => Socket::IPAddress.new("127.0.0.100", 8080)

If reuse_port is `true`, it enables the `SO_REUSEPORT` socket option, which allows multiple processes to bind to the same port.

def bind_tcp(port : Int32, reuse_port : Bool = false) : Socket::IPAddress #

Creates a `TCPServer` listening on `127.0.0.1:port` and adds it as a socket, returning the local address and port the server listens on.

    require "http/server"

    server = HTTP::Server.new { }
    server.bind_tcp(8080) # => Socket::IPAddress.new("127.0.0.1", 8080)

If reuse_port is `true`, it enables the `SO_REUSEPORT` socket option, which allows multiple processes to bind to the same port.

def bind_tcp(address : Socket::IPAddress, reuse_port : Bool = false) : Socket::IPAddress #

Creates a `TCPServer` listening on address and adds it as a socket, returning the local address and port the server listens on.

    require "http/server"

    server = HTTP::Server.new { }
    server.bind_tcp(Socket::IPAddress.new("127.0.0.100", 8080)) # => Socket::IPAddress.new("127.0.0.100", 8080)
    server.bind_tcp(Socket::IPAddress.new("127.0.0.100", 0))    # => Socket::IPAddress.new("127.0.0.100", 35487)

If reuse_port is `true`, it enables the `SO_REUSEPORT` socket option, which allows multiple processes to bind to the same port.

def bind_tls(host : String, port : Int32, context : OpenSSL::SSL::Context::Server, reuse_port : Bool = false) : Socket::IPAddress #

Creates an `OpenSSL::SSL::Server` and adds it as a socket.

The SSL server wraps a `TCPServer` listening on `host:port`.

    require "http/server"

    server = HTTP::Server.new { }
    context = OpenSSL::SSL::Context::Server.new
    context.certificate_chain = "openssl.crt"
    context.private_key = "openssl.key"
    server.bind_tls "127.0.0.1", 8080, context

def bind_tls(host : String, context : OpenSSL::SSL::Context::Server) : Socket::IPAddress #

Creates an `OpenSSL::SSL::Server` and adds it as a socket.

The SSL server wraps a `TCPServer` listening on an unused port on host.

    require "http/server"

    server = HTTP::Server.new { }
    context = OpenSSL::SSL::Context::Server.new
    context.certificate_chain = "openssl.crt"
    context.private_key = "openssl.key"
    address = server.bind_tls "127.0.0.1", context

def bind_tls(address : Socket::IPAddress, context : OpenSSL::SSL::Context::Server) : Socket::IPAddress #

Creates an `OpenSSL::SSL::Server` and adds it as a socket.

The SSL server wraps a `TCPServer` listening on an unused port on host.

    require "http/server"

    server = HTTP::Server.new { }
    context = OpenSSL::SSL::Context::Server.new
    context.certificate_chain = "openssl.crt"
    context.private_key = "openssl.key"
    address = server.bind_tls Socket::IPAddress.new("127.0.0.1", 8000), context

def bind_unix(path : String) : Socket::UNIXAddress #

Creates a `UNIXServer` bound to path and adds it as a socket.

    require "http/server"

    server = HTTP::Server.new { }
    server.bind_unix "/tmp/my-socket.sock"

def bind_unix(address : Socket::UNIXAddress) : Socket::UNIXAddress #

Creates a `UNIXServer` bound to address and adds it as a socket.

    require "http/server"

    server = HTTP::Server.new { }
    server.bind_unix(Socket::UNIXAddress.new("/tmp/my-socket.sock"))

def bind_unused_port(host : String = Socket::IPAddress::LOOPBACK, reuse_port : Bool = false) : Socket::IPAddress #

Creates a `TCPServer` listening on an unused port and adds it as a socket.

Returns the `Socket::IPAddress` with the determined port number.

    require "http/server"

    server = HTTP::Server.new { }
    server.bind_unused_port # => Socket::IPAddress.new("127.0.0.1", 12345)

def close : Nil #

Gracefully terminates the server. It will process currently accepted requests, but it won't accept new connections.

def closed? : Bool #

Returns `true` if this server is closed.

def each_address(&block : Socket::Address -> ) #

Enumerates all addresses this server is bound to.

def listen(port : Int32, reuse_port : Bool = false) #

Creates a `TCPServer` listening on `127.0.0.1:port`, adds it as a socket and starts the server. Blocks until the server is closed.

See `#bind(port : Int32)` for details.

def listen(host : String, port : Int32, reuse_port : Bool = false) #

Creates a `TCPServer` listening on `host:port`, adds it as a socket and starts the server. Blocks until the server is closed.

See `#bind(host : String, port : Int32)` for details.

def listen : Nil #

Starts the server. Blocks until the server is closed.

def listening? : Bool #

Returns `true` if this server is listening on its sockets.

def max_headers_size : Int32 #

Returns the maximum permitted combined size for the headers in an HTTP request.

When parsing a request, the server keeps track of the amount of total bytes consumed for all headers (including line breaks). If combined byte size of all headers is larger than the permitted size, the server responds with the status code `432 Request Header Fields Too Large` (see `HTTP::Status::REQUEST_HEADER_FIELDS_TOO_LARGE`).

Default: `HTTP::MAX_HEADERS_SIZE`

def max_headers_size=(size : Int32) #

Sets the maximum permitted combined size for the headers in an HTTP request.

def max_request_line_size : Int32 #

Returns the maximum permitted size for the request line in an HTTP request.

The request line is the first line of a request, consisting of method, resource and HTTP version and the delimiting line break. If the request line has a larger byte size than the permitted size, the server responds with the status code `414 URI Too Long` (see `HTTP::Status::URI_TOO_LONG`).

Default: `HTTP::MAX_REQUEST_LINE_SIZE`

def max_request_line_size=(size : Int32) #

Sets the maximum permitted size for the request line in an HTTP request.