Class: ServerSide::HTTP::Request

Inherits:
Object
  • Object
show all
Includes:
Static
Defined in:
lib/serverside/request.rb

Overview

The Request class encapsulates HTTP requests. The request class contains methods for parsing the request and rendering a response. HTTP requests are created by the connection. Descendants of HTTPRequest can be created When a connection is created, it creates new requests in a loop until the connection is closed.

Direct Known Subclasses

Router

Constant Summary collapse

LINE_BREAK =
"\r\n".freeze
REQUEST_REGEXP =

Here’s a nice one - parses the first line of a request. The expected format is as follows: <method> </path>[?<query>] HTTP/<version>

/([A-Za-z0-9]+)\s(\/[^\/\?]*(?:\/[^\/\?]+)*)\/?(?:\?(.*))?\sHTTP\/(.+)\r/.freeze
HEADER_REGEXP =

Regexp for parsing headers.

/([^:]+):\s?(.*)\r\n/.freeze
CONTENT_LENGTH =
'Content-Length'.freeze
VERSION_1_1 =
'1.1'.freeze
CONNECTION =
'Connection'.freeze
CLOSE =
'close'.freeze
AMPERSAND =
'&'.freeze
PARAMETER_REGEXP =

Regexp for parsing URI parameters.

/(.+)=(.*)/.freeze
EQUAL_SIGN =
'='.freeze
STATUS_CLOSE =
"HTTP/1.1 %d\r\nDate: %s\r\nConnection: close\r\nContent-Type: %s\r\n%s%sContent-Length: %d\r\n\r\n".freeze
STATUS_STREAM =
"HTTP/1.1 %d\r\nDate: %s\r\nConnection: close\r\nContent-Type: %s\r\n%s%s\r\n".freeze
STATUS_PERSIST =
"HTTP/1.1 %d\r\nDate: %s\r\nContent-Type: %s\r\n%s%sContent-Length: %d\r\n\r\n".freeze
STATUS_REDIRECT =
"HTTP/1.1 %d\r\nDate: %s\r\nConnection: close\r\nLocation: %s\r\n\r\n".freeze
HEADER =
"%s: %s\r\n".freeze
EMPTY_STRING =
''.freeze
EMPTY_HASH =
{}.freeze
SLASH =
'/'.freeze
LOCATION =
'Location'.freeze
'Cookie'
"Set-Cookie: %s=%s; path=/; expires=%s\r\n".freeze
/[;,] */n.freeze
/\s*(.+)=(.*)\s*/.freeze
Time.at(0).freeze
CONTENT_TYPE =
"Content-Type".freeze
CONTENT_TYPE_URL_ENCODED =
'application/x-www-form-urlencoded'.freeze
MULTIPART_REGEXP =
/multipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze
CONTENT_DISPOSITION_REGEXP =
/^Content-Disposition: form-data;([^\r]*)/m.freeze
FIELD_ATTRIBUTE_REGEXP =
/\s*(\w+)=\"([^\"]*)/.freeze
CONTENT_TYPE_REGEXP =
/^Content-Type: ([^\r]*)/m.freeze
CONTENT_DISPOSITION =
'Content-Disposition'.freeze
CONTENT_DESCRIPTION =
'Content-Description'.freeze

Constants included from Static

Static::DIR_LISTING, Static::DIR_LISTING_START, Static::DIR_LISTING_STOP, Static::ETAG_FORMAT, Static::FILE_NOT_FOUND, Static::MAX_AGE, Static::MAX_CACHE_FILE_SIZE, Static::RHTML, Static::TEXT_HTML, Static::TEXT_PLAIN

Constants included from Caching

Caching::CACHE_CONTROL, Caching::ETAG, Caching::ETAG_QUOTE_FORMAT, Caching::EXPIRES, Caching::EXPIRY_ETAG_FORMAT, Caching::EXPIRY_ETAG_REGEXP, Caching::IF_MODIFIED_SINCE, Caching::IF_NONE_MATCH, Caching::IF_NONE_MATCH_REGEXP, Caching::LAST_MODIFIED, Caching::NOT_MODIFIED_CLOSE, Caching::NOT_MODIFIED_PERSIST, Caching::NO_CACHE, Caching::VARY, Caching::WILDCARD

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Static

#serve_dir, #serve_file, #serve_static, #serve_template

Methods included from Caching

#disable_caching, #etag_validators, #expiry_etag, #send_not_modified_response, #valid_etag?, #valid_stamp?, #validate_cache

Constructor Details

#initialize(socket) ⇒ Request

Initializes the request instance. Any descendants of HTTP::Request which override the initialize method must receive socket as the single argument, and copy it to @socket.



55
56
57
58
# File 'lib/serverside/request.rb', line 55

def initialize(socket)
  @socket = socket
  @response_headers = {}
end

Instance Attribute Details

#bodyObject (readonly)

Returns the value of attribute body.



48
49
50
# File 'lib/serverside/request.rb', line 48

def body
  @body
end

#content_lengthObject (readonly)

Returns the value of attribute content_length.



48
49
50
# File 'lib/serverside/request.rb', line 48

def content_length
  @content_length
end

#content_typeObject (readonly)

Returns the value of attribute content_type.



48
49
50
# File 'lib/serverside/request.rb', line 48

def content_type
  @content_type
end

#cookiesObject (readonly)

Returns the value of attribute cookies.



48
49
50
# File 'lib/serverside/request.rb', line 48

def cookies
  @cookies
end

#headersObject (readonly)

Returns the value of attribute headers.



48
49
50
# File 'lib/serverside/request.rb', line 48

def headers
  @headers
end

#methodObject (readonly)

Returns the value of attribute method.



48
49
50
# File 'lib/serverside/request.rb', line 48

def method
  @method
end

#parametersObject (readonly)

Returns the value of attribute parameters.



48
49
50
# File 'lib/serverside/request.rb', line 48

def parameters
  @parameters
end

#pathObject (readonly)

Returns the value of attribute path.



48
49
50
# File 'lib/serverside/request.rb', line 48

def path
  @path
end

#persistentObject (readonly)

Returns the value of attribute persistent.



48
49
50
# File 'lib/serverside/request.rb', line 48

def persistent
  @persistent
end

#queryObject (readonly)

Returns the value of attribute query.



48
49
50
# File 'lib/serverside/request.rb', line 48

def query
  @query
end

#response_cookiesObject (readonly)

Returns the value of attribute response_cookies.



48
49
50
# File 'lib/serverside/request.rb', line 48

def response_cookies
  @response_cookies
end

#response_headersObject (readonly)

Returns the value of attribute response_headers.



48
49
50
# File 'lib/serverside/request.rb', line 48

def response_headers
  @response_headers
end

#socketObject (readonly)

Returns the value of attribute socket.



48
49
50
# File 'lib/serverside/request.rb', line 48

def socket
  @socket
end

#versionObject (readonly)

Returns the value of attribute version.



48
49
50
# File 'lib/serverside/request.rb', line 48

def version
  @version
end

Instance Method Details

Marks a cookie as deleted. The cookie is given an expires stamp in the past.



205
206
207
# File 'lib/serverside/request.rb', line 205

def delete_cookie(name)
  set_cookie(name, nil, COOKIE_EXPIRED_TIME)
end

#parseObject

Parses an HTTP request. If the request is not valid, nil is returned. Otherwise, the HTTP headers are returned. Also determines whether the connection is persistent (by checking the HTTP version and the ‘Connection’ header).



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/serverside/request.rb', line 69

def parse
  return nil unless @socket.gets =~ REQUEST_REGEXP
  @method, @path, @query, @version = $1.downcase.to_sym, $2, $3, $4
  @parameters = @query ? parse_parameters(@query) : {}
  @headers = {}
  while (line = @socket.gets)
    break if line.nil? || (line == LINE_BREAK)
    if line =~ HEADER_REGEXP
      @headers[$1.freeze] = $2.freeze
    end
  end
  @persistent = (@version == VERSION_1_1) && 
    (@headers[CONNECTION] != CLOSE)
  @cookies = @headers[COOKIE] ? parse_cookies : EMPTY_HASH
  @response_cookies = nil
  
  if @content_length = @headers[CONTENT_LENGTH].to_i
    @content_type = @headers[CONTENT_TYPE] || CONTENT_TYPE_URL_ENCODED
    @body = @socket.read(@content_length) rescue nil
    parse_body
  end
  
  @headers
end

#parse_bodyObject

parses the body, either by using



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
# File 'lib/serverside/request.rb', line 121

def parse_body
  if @content_type == CONTENT_TYPE_URL_ENCODED
    @parameters.merge! parse_parameters(@body)
  elsif @content_type =~ MULTIPART_REGEXP
    boundary = "--#$1"
    r = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r\n/m
    @body.split(r).each do |pt|
      headers, payload = pt.split("\r\n\r\n", 2)
      atts = {}
      if headers =~ CONTENT_DISPOSITION_REGEXP
        $1.split(';').map do |part|
          if part =~ FIELD_ATTRIBUTE_REGEXP
            atts[$1.to_sym] = $2
          end
        end
      end
      if headers =~ CONTENT_TYPE_REGEXP
        atts[:type] = $1
      end
      if name = atts[:name]
        atts[:content] = payload
        @parameters[name.to_sym] = atts[:filename] ? atts : atts[:content]
      end
    end
  end
end

#parse_cookiesObject

Parses cookie values passed in the request



106
107
108
109
110
111
112
113
# File 'lib/serverside/request.rb', line 106

def parse_cookies
  @headers[COOKIE].split(COOKIE_SPLIT).inject({}) do |m, i|
    if i =~ COOKIE_REGEXP
      m[$1.to_sym] = $2.uri_unescape
    end
    m
  end
end

#parse_parameters(query) ⇒ Object

Parses query parameters by splitting the query string and unescaping parameter values.



96
97
98
99
100
101
102
103
# File 'lib/serverside/request.rb', line 96

def parse_parameters(query)
  query.split(AMPERSAND).inject({}) do |m, i|
    if i =~ PARAMETER_REGEXP
      m[$1.to_sym] = $2.uri_unescape
    end
    m
  end
end

#processObject

Processes the request by parsing it and then responding.



61
62
63
# File 'lib/serverside/request.rb', line 61

def process
  parse && ((respond || true) && @persistent)
end

#redirect(location, permanent = false) ⇒ Object

Send a redirect response.



183
184
185
186
187
188
189
# File 'lib/serverside/request.rb', line 183

def redirect(location, permanent = false)
  @socket << (STATUS_REDIRECT % 
    [permanent ? 301 : 302, Time.now.httpdate, location])
rescue
ensure
  @persistent = false
end

#send_file(content, content_type, disposition = :inline, filename = nil, description = nil) ⇒ Object



173
174
175
176
177
178
179
180
# File 'lib/serverside/request.rb', line 173

def send_file(content, content_type, disposition = :inline, 
  filename = nil, description = nil)
  disposition = filename ?
    "#{disposition}; filename=#{filename}" : disposition
  @response_headers[CONTENT_DISPOSITION] = disposition
  @response_headers[CONTENT_DESCRIPTION] = description if description
  send_response(200, content_type, content)
end

#send_response(status, content_type, body = nil, content_length = nil, headers = nil) ⇒ Object

Sends an HTTP response.



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/serverside/request.rb', line 149

def send_response(status, content_type, body = nil, content_length = nil, 
  headers = nil)
  @response_headers.merge!(headers) if headers
  h = @response_headers.inject('') {|m, kv| m << (HEADER % kv)}
  
  # calculate content_length if needed. if we dont have the 
  # content_length, we consider the response as a streaming response, 
  # and so the connection will not be persistent.
  content_length = body.length if content_length.nil? && body
  @persistent = false if content_length.nil?
  
  # Select the right format to use according to circumstances.
  @socket << ((@persistent ? STATUS_PERSIST : 
    (body ? STATUS_CLOSE : STATUS_STREAM)) % 
    [status, Time.now.httpdate, content_type, h, @response_cookies, 
      content_length])
  @socket << body if body
rescue
  @persistent = false
end

Sets a cookie to be included in the response.



197
198
199
200
201
# File 'lib/serverside/request.rb', line 197

def set_cookie(name, value, expires)
  @response_cookies ||= ""
  @response_cookies <<
    (SET_COOKIE % [name, value.to_s.uri_escape, expires.rfc2822])
end

#stream(body) ⇒ Object

Streams additional data to the client.



192
193
194
# File 'lib/serverside/request.rb', line 192

def stream(body)
  (@socket << body if body) rescue (@persistent = false)
end