net-http-server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.*
3
+ LICENSE.txt
File without changes
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
@@ -0,0 +1 @@
1
+ --markup markdown --title "net-http-server Documentation" --protected
@@ -0,0 +1,8 @@
1
+ ### 0.1.0 / 2011-01-26
2
+
3
+ * Initial release:
4
+ * Added {Net::HTTP::Server::Parser}.
5
+ * Added {Net::HTTP::Server::Requests}.
6
+ * Added {Net::HTTP::Server::Responses}.
7
+ * Added {Net::HTTP::Server::Daemon}.
8
+ * Added {Rack::Handler::HTTP}.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Hal Brodigan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,48 @@
1
+ # net-http-server
2
+
3
+ * [Homepage](http://github.com/postmodern/net-http-server)
4
+ * [Issues](http://github.com/postmodern/net-http-server/issues)
5
+ * [Documentation](http://rubydoc.info/gems/net-http-server)
6
+ * Postmodern (postmodern.mod3 at gmail.com)
7
+
8
+ ## Description
9
+
10
+ {Net::HTTP::Server} is a pure Ruby HTTP server.
11
+
12
+ ## Features
13
+
14
+ * Pure Ruby.
15
+ * Provides a [Rack](http://rack.rubyforge.org/) Handler.
16
+
17
+ ## Examples
18
+
19
+ Simple HTTP Server:
20
+
21
+ require 'net/http/server'
22
+ require 'pp'
23
+
24
+ Net::HTTP::Server.run(:port => 8080) do |request,socket|
25
+ pp request
26
+
27
+ [200, {'Content-Type' => 'text/html'}, ['Hello World']]
28
+ end
29
+
30
+ Use it with Rack:
31
+
32
+ require 'rack/handler/http'
33
+
34
+ Rack::Handler::HTTP.run app
35
+
36
+ ## Requirements
37
+
38
+ * [parslet](http://rubygems.org/gems/parslet) ~> 1.0
39
+
40
+ ## Install
41
+
42
+ $ gem install net-http-server
43
+
44
+ ## Copyright
45
+
46
+ Copyright (c) 2011 Hal Brodigan
47
+
48
+ See {file:LICENSE.txt} for details.
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ gem 'ore-tasks', '~> 0.3.0'
6
+ require 'ore/tasks'
7
+
8
+ Ore::Tasks.new
9
+ rescue LoadError => e
10
+ STDERR.puts e.message
11
+ STDERR.puts "Run `gem install ore-tasks` to install 'ore/tasks'."
12
+ end
13
+
14
+ begin
15
+ gem 'rspec', '~> 2.4.0'
16
+ require 'rspec/core/rake_task'
17
+
18
+ RSpec::Core::RakeTask.new
19
+ rescue LoadError => e
20
+ task :spec do
21
+ abort "Please run `gem install rspec` to install RSpec."
22
+ end
23
+ end
24
+ task :test => :spec
25
+ task :default => :spec
26
+
27
+ begin
28
+ gem 'yard', '~> 0.6.0'
29
+ require 'yard'
30
+
31
+ YARD::Rake::YardocTask.new
32
+ rescue LoadError => e
33
+ task :yard do
34
+ abort "Please run `gem install yard` to install YARD."
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ name: net-http-server
2
+ summary: A pure Ruby HTTP Server
3
+ description: A Rack compatible pure Ruby HTTP Server.
4
+ license: MIT
5
+ authors: Postmodern
6
+ email: postmodern.mod3@gmail.com
7
+ homepage: http://github.com/postmodern/net-http-server
8
+ has_yard: true
9
+
10
+ dependencies:
11
+ parslet: ~> 1.0
12
+
13
+ development_dependencies:
14
+ ore-tasks: ~> 0.3.0
15
+ rspec: ~> 2.4.0
16
+ yard: ~> 0.6.0
@@ -0,0 +1,4 @@
1
+ require 'net/http/server/parser'
2
+ require 'net/http/server/daemon'
3
+ require 'net/http/server/server'
4
+ require 'net/http/server/version'
@@ -0,0 +1,123 @@
1
+ require 'net/http/server/parser'
2
+ require 'net/http/server/requests'
3
+ require 'net/http/server/responses'
4
+
5
+ require 'net/protocol'
6
+ require 'gserver'
7
+
8
+ module Net
9
+ class HTTP < Protocol
10
+ module Server
11
+ class Daemon < GServer
12
+
13
+ include Requests
14
+ include Responses
15
+
16
+ # Default host to bind to.
17
+ DEFAULT_HOST = '0.0.0.0'
18
+
19
+ # Default port to listen on.
20
+ DEFAULT_PORT = 8080
21
+
22
+ # Maximum number of simultaneous connections.
23
+ MAX_CONNECTIONS = 256
24
+
25
+ # Creates a new HTTP Daemon.
26
+ #
27
+ # @param [Hash] options
28
+ # Options for the daemon.
29
+ #
30
+ # @option options [String] :host (DEFAULT_HOST)
31
+ # The host to run on.
32
+ #
33
+ # @option options [String] :port (DEFAULT_PORT)
34
+ # The port to listen on.
35
+ #
36
+ # @option options [Integer] :max_connections (MAX_CONNECTIONS)
37
+ # The maximum number of simultaneous connections.
38
+ #
39
+ # @option options [IO] :log (STDERR)
40
+ # The log to write errors to.
41
+ #
42
+ # @option options [#call] :handler
43
+ # The HTTP Request Handler object.
44
+ #
45
+ # @yield [request, socket]
46
+ # If a block is given, it will be used to process HTTP Requests.
47
+ #
48
+ # @yieldparam [Hash{Symbol => String,Array,Hash}] request
49
+ # The HTTP Request.
50
+ #
51
+ # @yieldparam [TCPSocket] socket
52
+ # The TCP socket of the client.
53
+ #
54
+ def initialize(options={},&block)
55
+ host = options.fetch(:host,DEFAULT_HOST)
56
+ port = options.fetch(:port,DEFAULT_PORT).to_i
57
+ max_connections = options.fetch(:max_connections,MAX_CONNECTIONS)
58
+ log = options.fetch(:log,STDERR)
59
+
60
+ super(port,host,max_connections,log,false,true)
61
+
62
+ handler(options[:handler],&block)
63
+ end
64
+
65
+ #
66
+ # Sets the HTTP Request Handler.
67
+ #
68
+ # @param [#call, nil] object
69
+ # The HTTP Request Handler object.
70
+ #
71
+ # @yield [request, socket]
72
+ # If a block is given, it will be used to process HTTP Requests.
73
+ #
74
+ # @yieldparam [Hash{Symbol => String,Array,Hash}] request
75
+ # The HTTP Request.
76
+ #
77
+ # @yieldparam [TCPSocket] socket
78
+ # The TCP socket of the client.
79
+ #
80
+ # @raise [ArgumentError]
81
+ # The HTTP Request Handler must respond to `#call`.
82
+ #
83
+ def handler(object=nil,&block)
84
+ if object
85
+ unless object.respond_to?(:call)
86
+ raise(ArgumentError,"HTTP Request Handler must respond to #call")
87
+ end
88
+ elsif block.nil?
89
+ raise(ArgumentError,"no HTTP Request Handler block given")
90
+ end
91
+
92
+ @handler = (object || block)
93
+ end
94
+
95
+ #
96
+ # Receives HTTP Requests and handles them.
97
+ #
98
+ # @param [TCPSocket] socket
99
+ # A new TCP connection.
100
+ #
101
+ def serve(socket)
102
+ if (raw_request = read_request(socket))
103
+ parser = Parser.new
104
+
105
+ begin
106
+ request = parser.parse(raw_request)
107
+ rescue Parslet::ParseFailed => error
108
+ return Responses::BAD_REQUEST
109
+ end
110
+
111
+ normalize_request(request)
112
+
113
+ # rack compliant
114
+ status, headers, body = @handler.call(request,socket)
115
+
116
+ write_response(socket,status,headers,body)
117
+ end
118
+ end
119
+
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,151 @@
1
+ require 'net/protocol'
2
+ require 'parslet'
3
+
4
+ module Net
5
+ class HTTP < Protocol
6
+ module Server
7
+ #
8
+ # Inspired by:
9
+ #
10
+ # * [Thin](https://github.com/macournoyer/thin/blob/master/ext/thin_parser/common.rl)
11
+ # * [Unicorn](https://github.com/defunkt/unicorn/blob/master/ext/unicorn_http/unicorn_http_common.rl)
12
+ # * [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616.html)
13
+ #
14
+ class Parser < Parslet::Parser
15
+
16
+ #
17
+ # Character Classes
18
+ #
19
+ rule(:digit) { match['0-9'] }
20
+ rule(:digits) { digit.repeat(1) }
21
+ rule(:xdigit) { digit | match['a-fA-F'] }
22
+ rule(:upper) { match['A-Z'] }
23
+ rule(:lower) { match['a-z'] }
24
+ rule(:alpha) { upper | lower }
25
+ rule(:alnum) { alpha | digit }
26
+ rule(:cntrl) { match['\x00-\x1f'] }
27
+ rule(:ascii) { match['\x00-\x7f'] }
28
+
29
+ rule(:lws) { match[" \t"] }
30
+ rule(:crlf) { str("\r\n") }
31
+
32
+ rule(:ctl) { cntrl | str("\x7f") }
33
+ rule(:text) { lws | (ctl.absnt? >> ascii) }
34
+
35
+ rule(:safe) { charset('$', '-', '_', '.') }
36
+ rule(:extra) { charset('!', '*', "'", '(', ')', ',') }
37
+ rule(:reserved) { charset(';', '/', '?', ':', '@', '&', '=', '+') }
38
+ rule(:sorta_safe) { charset('"', '<', '>') }
39
+
40
+ rule(:unsafe) { ctl | charset(' ', '#', '%') | sorta_safe }
41
+ rule(:national) {
42
+ (alpha | digit | reserved | extra | safe | unsafe).absnt? >> any
43
+ }
44
+
45
+ rule(:unreserved) { alpha | digit | safe | extra | national }
46
+ rule(:escape) { str("%u").maybe >> xdigit >> xdigit }
47
+ rule(:uchar) { unreserved | escape | sorta_safe }
48
+ rule(:pchar) { uchar | charset(':', '@', '&', '=', '+') }
49
+ rule(:separators) {
50
+ lws | charset(
51
+ '(', ')', '<', '>', '@', ',', ';', ':', "\\", '"', '/', '[', ']',
52
+ '?', '=', '{', '}'
53
+ )
54
+ }
55
+
56
+ #
57
+ # Elements
58
+ #
59
+ rule(:token) { (ctl | separators).absnt? >> ascii }
60
+
61
+ rule(:comment_text) { (str('(') | str(')')).absnt? >> text }
62
+ rule(:comment) { str('(') >> comment_text.repeat >> str(')') }
63
+
64
+ rule(:quoted_pair) { str("\\") >> ascii }
65
+ rule(:quoted_text) { quoted_pair | str('"').absnt? >> text }
66
+ rule(:quoted_string) { str('"') >> quoted_text >> str('"') }
67
+
68
+ #
69
+ # URI Elements
70
+ #
71
+ rule(:scheme) {
72
+ (alpha | digit | charset('+', '-', '.')).repeat
73
+ }
74
+ rule(:host_name) {
75
+ (alnum | charset('-', '_', '.')).repeat(1)
76
+ }
77
+ rule(:user_info) {
78
+ (
79
+ unreserved | escape | charset(';', ':', '&', '=', '+')
80
+ ).repeat(1)
81
+ }
82
+
83
+ rule(:path) { pchar.repeat(1) >> (str('/') >> pchar.repeat).repeat }
84
+ rule(:query_string) { (uchar | reserved).repeat }
85
+ rule(:param) { (pchar | str('/')).repeat }
86
+ rule(:params) { param >> (str(';') >> param).repeat }
87
+ rule(:frag) { (uchar | reserved).repeat }
88
+
89
+ rule(:relative_path) {
90
+ path.maybe.as(:path) >>
91
+ (str(';') >> params.as(:params)).maybe >>
92
+ (str('?') >> query_string.as(:query)).maybe >>
93
+ (str('#') >> frag.as(:fragment)).maybe
94
+ }
95
+ rule(:absolute_path) { str('/').repeat(1) >> relative_path }
96
+
97
+ rule(:absolute_uri) {
98
+ scheme.as(:scheme) >> str(':') >> str('//').maybe >>
99
+ (user_info.as(:user_info) >> str('@')).maybe >>
100
+ host_name.as(:host) >>
101
+ (str(':') >> digits.as(:port)).maybe >>
102
+ absolute_path
103
+ }
104
+
105
+ rule(:request_uri) { str('*') | absolute_uri | absolute_path }
106
+
107
+ #
108
+ # HTTP Elements
109
+ #
110
+ rule(:request_method) { upper.repeat(1,20) | token.repeat(1) }
111
+
112
+ rule(:version_number) { digits >> str('.') >> digits }
113
+ rule(:http_version) { str('HTTP/') >> version_number.as(:version) }
114
+ rule(:request_line) {
115
+ request_method.as(:method) >>
116
+ str(' ') >> request_uri.as(:uri) >>
117
+ str(' ') >> http_version
118
+ }
119
+
120
+ rule(:header_name) { (str(':').absnt? >> token).repeat(1) }
121
+ rule(:header_value) {
122
+ (text | token | separators | quoted_string).repeat(1)
123
+ }
124
+
125
+ rule(:header) {
126
+ header_name.as(:name) >> str(':') >> lws.repeat(1) >>
127
+ header_value.as(:value) >> crlf
128
+ }
129
+ rule(:request) {
130
+ request_line >> crlf >>
131
+ header.repeat.as(:headers) >> crlf
132
+ }
133
+
134
+ root :request
135
+
136
+ protected
137
+
138
+ #
139
+ # Creates a matcher for the given characters.
140
+ #
141
+ # @param [Array<String>] chars
142
+ # The characters to match.
143
+ #
144
+ def charset(*chars)
145
+ match[chars.map { |c| Regexp.escape(c) }.join]
146
+ end
147
+
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,129 @@
1
+ require 'net/protocol'
2
+
3
+ module Net
4
+ class HTTP < Protocol
5
+ module Server
6
+ module Requests
7
+ # Default ports for common URI schemes
8
+ DEFAULT_PORTS = {
9
+ 'https' => 443,
10
+ 'http' => 80
11
+ }
12
+
13
+ protected
14
+
15
+ #
16
+ # Reads a HTTP Request from the stream.
17
+ #
18
+ # @param [IO] stream
19
+ # The stream to read from.
20
+ #
21
+ # @return [String, nil]
22
+ # The raw HTTP Request or `nil` if the Request was malformed.
23
+ #
24
+ def read_request(stream)
25
+ buffer = ''
26
+
27
+ request_line = stream.readline("\r\n")
28
+
29
+ # the request line must contain 'HTTP/'
30
+ return unless request_line.include?('HTTP/')
31
+
32
+ buffer << request_line
33
+
34
+ stream.each_line("\r\n") do |header|
35
+ buffer << header
36
+
37
+ # a header line must contain a ':' character followed by
38
+ # linear-white-space (either ' ' or "\t").
39
+ unless (header.include?(': ') || header.include?(":\t"))
40
+ # if this is not a header line, check if it is the end
41
+ # of the request
42
+ if header == "\r\n"
43
+ # end of the request
44
+ break
45
+ else
46
+ # invalid header line
47
+ return
48
+ end
49
+ end
50
+ end
51
+
52
+ return buffer
53
+ end
54
+
55
+ #
56
+ # Normalizes the `:uri` part of the request.
57
+ #
58
+ # @param [Hash] request
59
+ # The unnormalized HTTP request.
60
+ #
61
+ def normalize_uri(request)
62
+ uri = request[:uri]
63
+
64
+ if uri.kind_of?(Hash)
65
+ if uri[:scheme]
66
+ uri[:port] = unless uri[:port]
67
+ DEFAULT_PORTS[uri[:scheme]]
68
+ else
69
+ uri[:port].to_i
70
+ end
71
+ end
72
+
73
+ unless uri[:path]
74
+ uri[:path] = '/'
75
+ else
76
+ uri[:path].insert(0,'/')
77
+ end
78
+ elsif uri == '*'
79
+ request[:uri] = {}
80
+ end
81
+ end
82
+
83
+ #
84
+ # Normalizes the `:headers` part of the request.
85
+ #
86
+ # @param [Hash] request
87
+ # The unnormalized HTTP request.
88
+ #
89
+ def normalize_headers(request)
90
+ headers = request[:headers]
91
+ normalized_headers = {}
92
+
93
+ unless headers.empty?
94
+ headers.each do |header|
95
+ name = header[:name]
96
+ value = header[:value]
97
+
98
+ if normalized_headers.has_key?(name)
99
+ previous_value = normalized_headers[name]
100
+
101
+ if previous_value.kind_of?(Array)
102
+ previous_value << value
103
+ else
104
+ normalized_headers[name] = [previous_value, value]
105
+ end
106
+ else
107
+ normalized_headers[name] = value
108
+ end
109
+ end
110
+ end
111
+
112
+ request[:headers] = normalized_headers
113
+ end
114
+
115
+ #
116
+ # Normalizes a HTTP request.
117
+ #
118
+ # @param [Hash] request
119
+ # The unnormalized HTTP request.
120
+ #
121
+ def normalize_request(request)
122
+ normalize_uri(request)
123
+ normalize_headers(request)
124
+ end
125
+
126
+ end
127
+ end
128
+ end
129
+ end