ferencd@0: require 'net/http/server/chunked_stream' ferencd@0: ferencd@0: require 'net/protocol' ferencd@0: require 'time' ferencd@0: ferencd@0: module Net ferencd@0: class HTTP < Protocol ferencd@0: module Server ferencd@0: module Responses ferencd@0: # The supported HTTP Protocol. ferencd@0: HTTP_VERSION = '1.1' ferencd@0: ferencd@0: # The known HTTP Status codes and messages ferencd@0: HTTP_STATUSES = { ferencd@0: # 1xx ferencd@0: 100 => 'Continue', ferencd@0: 101 => 'Switching Protocols', ferencd@0: 102 => 'Processing', ferencd@0: # 2xx ferencd@0: 200 => 'OK', ferencd@0: 201 => 'Created', ferencd@0: 202 => 'Accepted', ferencd@0: 203 => 'Non-Authoritative Information', ferencd@0: 204 => 'No Content', ferencd@0: 205 => 'Reset Content', ferencd@0: 206 => 'Partial Content', ferencd@0: # 3xx ferencd@0: 300 => 'Multiple Choices', ferencd@0: 301 => 'Moved Permanently', ferencd@0: 302 => 'Found', ferencd@0: 303 => 'See Other', ferencd@0: 304 => 'Not Modified', ferencd@0: 305 => 'Use Proxy', ferencd@0: 307 => 'Temporary Redirect', ferencd@0: # 4xx ferencd@0: 400 => 'Bad Request', ferencd@0: 401 => 'Unauthorized', ferencd@0: 402 => 'Payment Required', ferencd@0: 403 => 'Forbidden', ferencd@0: 404 => 'Not Found', ferencd@0: 405 => 'Method Not Allowed', ferencd@0: 406 => 'Not Acceptable', ferencd@0: 407 => 'Proxy Authentication Required', ferencd@0: 408 => 'Request Time-out', ferencd@0: 409 => 'Conflict', ferencd@0: 410 => 'Gone', ferencd@0: 411 => 'Length Required', ferencd@0: 412 => 'Precondition Failed', ferencd@0: 413 => 'Request Entity Too Large', ferencd@0: 414 => 'Request-URI Too Large', ferencd@0: 415 => 'Unsupported Media Type', ferencd@0: 416 => 'Requested range not satisfiable', ferencd@0: 417 => 'Expectation Failed', ferencd@0: # 5xx ferencd@0: 500 => 'Internal Server Error', ferencd@0: 501 => 'Not Implemented', ferencd@0: 502 => 'Bad Gateway', ferencd@0: 503 => 'Service Unavailable', ferencd@0: 504 => 'Gateway Time-out', ferencd@0: 505 => 'HTTP Version not supported extension-code' ferencd@0: } ferencd@0: ferencd@0: # Generic Bad Request response ferencd@0: BAD_REQUEST = [400, {}, ['Bad Request']] ferencd@0: ferencd@0: protected ferencd@0: ferencd@0: # ferencd@0: # Writes the status of an HTTP Response to a stream. ferencd@0: # ferencd@0: # @param [IO] stream ferencd@0: # The stream to write the headers back to. ferencd@0: # ferencd@0: # @param [Integer] status ferencd@0: # The status of the HTTP Response. ferencd@0: # ferencd@0: def write_status(stream,status) ferencd@0: status = status.to_i ferencd@0: ferencd@0: reason = HTTP_STATUSES[status] ferencd@0: stream.write("HTTP/#{HTTP_VERSION} #{status} #{reason}\r\n") ferencd@0: end ferencd@0: ferencd@0: # ferencd@0: # Write the headers of an HTTP Response to a stream. ferencd@0: # ferencd@0: # @param [IO] stream ferencd@0: # The stream to write the headers back to. ferencd@0: # ferencd@0: # @param [Hash{String => String,Time,Array}] headers ferencd@0: # The headers of the HTTP Response. ferencd@0: # ferencd@0: def write_headers(stream,headers) ferencd@0: headers.each do |name,values| ferencd@0: case values ferencd@0: when String ferencd@0: values.each_line("\n") do |value| ferencd@0: stream.write("#{name}: #{value.chomp}\r\n") ferencd@0: end ferencd@0: when Time ferencd@0: stream.write("#{name}: #{values.httpdate}\r\n") ferencd@0: when Array ferencd@0: values.each do |value| ferencd@0: stream.write("#{name}: #{value}\r\n") ferencd@0: end ferencd@0: end ferencd@0: end ferencd@0: ferencd@0: stream.write("\r\n") ferencd@0: stream.flush ferencd@0: end ferencd@0: ferencd@0: # ferencd@0: # Writes the body of a HTTP Response to a stream. ferencd@0: # ferencd@0: # @param [IO] stream ferencd@0: # The stream to write the headers back to. ferencd@0: # ferencd@0: # @param [#each] body ferencd@0: # The body of the HTTP Response. ferencd@0: # ferencd@0: def write_body(stream,body) ferencd@0: body.each do |chunk| ferencd@0: stream.write(chunk) ferencd@0: stream.flush ferencd@0: end ferencd@0: end ferencd@0: ferencd@0: # ferencd@0: # Writes the body of a HTTP Response to a stream, using Chunked ferencd@0: # Transfer-Encoding. ferencd@0: # ferencd@0: # @param [IO] stream ferencd@0: # The stream to write the headers back to. ferencd@0: # ferencd@0: # @param [#each] body ferencd@0: # The body of the HTTP Response. ferencd@0: # ferencd@0: # @since 0.2.0 ferencd@0: # ferencd@0: def write_body_streamed(stream,body) ferencd@0: chunked_stream = ChunkedStream.new(stream) ferencd@0: ferencd@0: body.each { |chunk| chunked_stream.write(chunk) } ferencd@0: ferencd@0: chunked_stream.close ferencd@0: end ferencd@0: ferencd@0: # ferencd@0: # Writes a HTTP Response to a stream. ferencd@0: # ferencd@0: # @param [IO] stream ferencd@0: # The stream to write the HTTP Response to. ferencd@0: # ferencd@0: # @param [Integer] status ferencd@0: # The status of the HTTP Response. ferencd@0: # ferencd@0: # @param [Hash{String => String,Time,Array}] headers ferencd@0: # The headers of the HTTP Response. ferencd@0: # ferencd@0: # @param [#each] body ferencd@0: # The body of the HTTP Response. ferencd@0: # ferencd@0: def write_response(stream,status,headers,body) ferencd@0: write_status stream, status ferencd@0: write_headers stream, headers ferencd@0: ferencd@0: if headers['Transfer-Encoding'] == 'chunked' ferencd@0: write_body_streamed stream, body ferencd@0: else ferencd@0: write_body stream, body ferencd@0: ferencd@0: # if neither `Content-Length` or `Transfer-Encoding` ferencd@0: # were specified, close the stream after writing the response. ferencd@0: stream.close unless headers['Content-Length'] ferencd@0: end ferencd@0: end ferencd@0: ferencd@0: end ferencd@0: end ferencd@0: end ferencd@0: end