net-http-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,151 @@
1
+ require 'net/protocol'
2
+ require 'time'
3
+
4
+ module Net
5
+ class HTTP < Protocol
6
+ module Server
7
+ module Responses
8
+ # The supported HTTP Protocol.
9
+ HTTP_VERSION = '1.1'
10
+
11
+ # The known HTTP Status codes and messages
12
+ HTTP_STATUSES = {
13
+ # 1xx
14
+ 100 => 'Continue',
15
+ 101 => 'Switching Protocols',
16
+ 102 => 'Processing',
17
+ # 2xx
18
+ 200 => 'OK',
19
+ 201 => 'Created',
20
+ 202 => 'Accepted',
21
+ 203 => 'Non-Authoritative Information',
22
+ 204 => 'No Content',
23
+ 205 => 'Reset Content',
24
+ 206 => 'Partial Content',
25
+ # 3xx
26
+ 300 => 'Multiple Choices',
27
+ 301 => 'Moved Permanently',
28
+ 302 => 'Found',
29
+ 303 => 'See Other',
30
+ 304 => 'Not Modified',
31
+ 305 => 'Use Proxy',
32
+ 307 => 'Temporary Redirect',
33
+ # 4xx
34
+ 400 => 'Bad Request',
35
+ 401 => 'Unauthorized',
36
+ 402 => 'Payment Required',
37
+ 403 => 'Forbidden',
38
+ 404 => 'Not Found',
39
+ 405 => 'Method Not Allowed',
40
+ 406 => 'Not Acceptable',
41
+ 407 => 'Proxy Authentication Required',
42
+ 408 => 'Request Time-out',
43
+ 409 => 'Conflict',
44
+ 410 => 'Gone',
45
+ 411 => 'Length Required',
46
+ 412 => 'Precondition Failed',
47
+ 413 => 'Request Entity Too Large',
48
+ 414 => 'Request-URI Too Large',
49
+ 415 => 'Unsupported Media Type',
50
+ 416 => 'Requested range not satisfiable',
51
+ 417 => 'Expectation Failed',
52
+ # 5xx
53
+ 500 => 'Internal Server Error',
54
+ 501 => 'Not Implemented',
55
+ 502 => 'Bad Gateway',
56
+ 503 => 'Service Unavailable',
57
+ 504 => 'Gateway Time-out',
58
+ 505 => 'HTTP Version not supported extension-code'
59
+ }
60
+
61
+ # Generic Bad Request response
62
+ BAD_REQUEST = [400, {}, ['Bad Request']]
63
+
64
+ protected
65
+
66
+ #
67
+ # Writes the status of an HTTP Response to a stream.
68
+ #
69
+ # @param [IO] stream
70
+ # The stream to write the headers back to.
71
+ #
72
+ # @param [Integer] status
73
+ # The status of the HTTP Response.
74
+ #
75
+ def write_status(stream,status)
76
+ status = status.to_i
77
+
78
+ reason = HTTP_STATUSES[status]
79
+ stream.write("HTTP/#{HTTP_VERSION} #{status} #{reason}\r\n")
80
+ end
81
+
82
+ #
83
+ # Write the headers of an HTTP Response to a stream.
84
+ #
85
+ # @param [IO] stream
86
+ # The stream to write the headers back to.
87
+ #
88
+ # @param [Hash{String => [String, Time, Array<String>}] headers
89
+ # The headers of the HTTP Response.
90
+ #
91
+ def write_headers(stream,headers)
92
+ headers.each do |name,values|
93
+ case values
94
+ when String
95
+ values.each_line("\n") do |value|
96
+ stream.write("#{name}: #{value.chomp}\r\n")
97
+ end
98
+ when Time
99
+ stream.write("#{name}: #{values.httpdate}\r\n")
100
+ when Array
101
+ values.each do |value|
102
+ stream.write("#{name}: #{value}\r\n")
103
+ end
104
+ end
105
+ end
106
+
107
+ stream.write("\r\n")
108
+ stream.flush
109
+ end
110
+
111
+ #
112
+ # Writes the body of a HTTP Response to a stream.
113
+ #
114
+ # @param [IO] stream
115
+ # The stream to write the headers back to.
116
+ #
117
+ # @param [#each] body
118
+ # The body of the HTTP Response.
119
+ #
120
+ def write_body(stream,body)
121
+ body.each do |chunk|
122
+ stream.write(chunk)
123
+ stream.flush
124
+ end
125
+ end
126
+
127
+ #
128
+ # Writes a HTTP Response to a stream.
129
+ #
130
+ # @param [IO] stream
131
+ # The stream to write the HTTP Response to.
132
+ #
133
+ # @param [Integer] status
134
+ # The status of the HTTP Response.
135
+ #
136
+ # @param [Hash{String => [String, Time, Array<String>}] headers
137
+ # The headers of the HTTP Response.
138
+ #
139
+ # @param [#each] body
140
+ # The body of the HTTP Response.
141
+ #
142
+ def write_response(stream,status,headers,body)
143
+ write_status stream, status
144
+ write_headers stream, headers
145
+ write_body stream, body
146
+ end
147
+
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,49 @@
1
+ require 'net/http/server/daemon'
2
+
3
+ require 'net/protocol'
4
+
5
+ module Net
6
+ class HTTP < Protocol
7
+ module Server
8
+ #
9
+ # Starts the HTTP Server.
10
+ #
11
+ # @param [Hash] options
12
+ # Options for the server.
13
+ #
14
+ # @option options [String] :host (DEFAULT_HOST)
15
+ # The host to run on.
16
+ #
17
+ # @option options [String] :port (DEFAULT_PORT)
18
+ # The port to listen on.
19
+ #
20
+ # @option options [Integer] :max_connections (MAX_CONNECTIONS)
21
+ # The maximum number of simultaneous connections.
22
+ #
23
+ # @option options [Boolean] :background (false)
24
+ # Specifies whether to run the server in the background or
25
+ # foreground.
26
+ #
27
+ # @option options [#call] :handler
28
+ # The HTTP Request Handler object.
29
+ #
30
+ # @yield [request, socket]
31
+ # If a block is given, it will be used to process HTTP Requests.
32
+ #
33
+ # @yieldparam [Hash{Symbol => String,Array,Hash}] request
34
+ # The HTTP Request.
35
+ #
36
+ # @yieldparam [TCPSocket] socket
37
+ # The TCP socket of the client.
38
+ #
39
+ def Server.run(options={},&block)
40
+ daemon = Daemon.new(options,&block)
41
+
42
+ daemon.start
43
+ daemon.join unless options[:background]
44
+ return daemon
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,10 @@
1
+ require 'net/protocol'
2
+
3
+ module Net
4
+ class HTTP < Protocol
5
+ module Server
6
+ # net-http-server version.
7
+ VERSION = '0.1.0'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,170 @@
1
+ require 'net/http/server/daemon'
2
+ require 'net/http/server/version'
3
+
4
+ require 'rack'
5
+ require 'set'
6
+
7
+ module Rack
8
+ module Handler
9
+ #
10
+ # A Rack handler for {Net::HTTP::Server}.
11
+ #
12
+ class HTTP
13
+
14
+ # The default environment settings.
15
+ DEFAULT_ENV = {
16
+ 'rack.version' => Rack::VERSION,
17
+ 'rack.errors' => STDERR,
18
+ 'rack.multithread' => true,
19
+ 'rack.multiprocess' => false,
20
+ 'rack.run_once' => false,
21
+ 'rack.url_scheme' => 'http',
22
+
23
+ 'SERVER_SOFTWARE' => "Net::HTTP::Server/#{Net::HTTP::Server::VERSION} (Ruby/#{RUBY_VERSION}/#{RUBY_RELEASE_DATE})",
24
+ 'SCRIPT_NAME' => ''
25
+ }
26
+
27
+ # Special HTTP Headers used by Rack::Request
28
+ SPECIAL_HEADERS = Set[
29
+ 'Content-Type',
30
+ 'Content-Length',
31
+ ]
32
+
33
+ #
34
+ # Initializes the handler.
35
+ #
36
+ # @param [#call] app
37
+ # The application the handler will be passing requests to.
38
+ #
39
+ # @param [Hash] options
40
+ # Additional options.
41
+ #
42
+ # @option options [String] :Host
43
+ # The host to bind to.
44
+ #
45
+ # @option options [Integer] :Port
46
+ # The port to listen on.
47
+ #
48
+ def initialize(app,options={})
49
+ @app = app
50
+ @options = options
51
+
52
+ @server = nil
53
+ end
54
+
55
+ #
56
+ # Creates a new handler and begins handling HTTP Requests.
57
+ #
58
+ # @see #initialize
59
+ #
60
+ def self.run(app,options={})
61
+ new(app,options).run
62
+ end
63
+
64
+ #
65
+ # Starts {Net::HTTP::Server} and begins handling HTTP Requests.
66
+ #
67
+ def run
68
+ @server = Net::HTTP::Server::Daemon.new(
69
+ :host => @options[:Host],
70
+ :port => @options[:Port],
71
+ :handler => self
72
+ )
73
+
74
+ @server.start
75
+ @server.join
76
+ end
77
+
78
+ #
79
+ # Handles an HTTP Request.
80
+ #
81
+ # @param [Hash] request
82
+ # An HTTP Request received from {Net::HTTP::Server}.
83
+ #
84
+ # @param [TCPSocket] socket
85
+ # The socket that the request was received from.
86
+ #
87
+ # @return [Array<Integer, Hash, Array>]
88
+ # The response status, headers and body.
89
+ #
90
+ def call(request,socket)
91
+ request_uri = request[:uri]
92
+ remote_address = socket.remote_address
93
+ local_address = socket.local_address
94
+
95
+ env = {}
96
+
97
+ # add the default values
98
+ env.merge!(DEFAULT_ENV)
99
+
100
+ # populate
101
+ env['rack.input'] = socket
102
+
103
+ if request_uri[:scheme]
104
+ env['rack.url_scheme'] = request_uri[:scheme]
105
+ end
106
+
107
+ env['SERVER_NAME'] = local_address.getnameinfo[0]
108
+ env['SERVER_PORT'] = local_address.ip_port.to_s
109
+ env['SERVER_PROTOCOL'] = "HTTP/#{request[:http_version]}"
110
+
111
+ env['REMOTE_ADDR'] = remote_address.ip_address
112
+ env['REMOTE_PORT'] = remote_address.ip_port.to_s
113
+
114
+ env['REQUEST_METHOD'] = request[:method]
115
+ env['PATH_INFO'] = request_uri.fetch(:path,'*')
116
+ env['QUERY_STRING'] = request_uri[:query_string].to_s
117
+
118
+ # add the headers
119
+ request[:headers].each do |name,value|
120
+ key = name.dup
121
+
122
+ key.upcase!
123
+ key.tr!('-','_')
124
+
125
+ # if the header is not special, prepend 'HTTP_'
126
+ unless SPECIAL_HEADERS.include?(name)
127
+ key.insert(0,'HTTP_')
128
+ end
129
+
130
+ env[key] = case value
131
+ when Array
132
+ value.join("\n")
133
+ else
134
+ value.to_s
135
+ end
136
+ end
137
+
138
+ @app.call(env)
139
+ end
140
+
141
+ #
142
+ # Determines if the handler is running.
143
+ #
144
+ # @return [Boolean]
145
+ # Specifies whether the handler is still running.
146
+ #
147
+ def running?
148
+ @server && !(@server.stopped?)
149
+ end
150
+
151
+ #
152
+ # Determines whether the handler was stopped.
153
+ #
154
+ # @return [Boolean]
155
+ # Specifies whether the handler was previously stopped.
156
+ #
157
+ def stopped?
158
+ @server.nil? || @server.stopped?
159
+ end
160
+
161
+ #
162
+ # Stops the handler.
163
+ #
164
+ def stop
165
+ @server.stop if @server
166
+ end
167
+
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,10 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ begin
4
+ Ore::Specification.new do |gemspec|
5
+ # custom logic here
6
+ end
7
+ rescue NameError
8
+ STDERR.puts "The 'net-http-server.gemspec' file requires Ore."
9
+ STDERR.puts "Run `gem install ore-core` to install Ore."
10
+ end
@@ -0,0 +1,69 @@
1
+ require 'net/http'
2
+ require 'yaml'
3
+
4
+ # Borrowed from Rack's own specs.
5
+ class TestRequest
6
+ def call(env)
7
+ status = env["QUERY_STRING"] =~ /secret/ ? 403 : 200
8
+ env["test.postdata"] = env["rack.input"].read
9
+ body = env.to_yaml
10
+ size = body.respond_to?(:bytesize) ? body.bytesize : body.size
11
+ [status, {"Content-Type" => "text/yaml", "Content-Length" => size.to_s}, [body]]
12
+ end
13
+
14
+ module Helpers
15
+ attr_reader :status, :response
16
+
17
+ ROOT = File.expand_path(File.dirname(__FILE__) + "/..")
18
+ ENV["RUBYOPT"] = "-I#{ROOT}/lib -rubygems"
19
+
20
+ def root
21
+ ROOT
22
+ end
23
+
24
+ def rackup
25
+ "#{ROOT}/bin/rackup"
26
+ end
27
+
28
+ def GET(path, header={})
29
+ Net::HTTP.start(@host, @port) { |http|
30
+ user = header.delete(:user)
31
+ passwd = header.delete(:passwd)
32
+
33
+ get = Net::HTTP::Get.new(path, header)
34
+ get.basic_auth user, passwd if user && passwd
35
+ http.request(get) { |response|
36
+ @status = response.code.to_i
37
+ if response.content_type == "text/yaml"
38
+ load_yaml(response)
39
+ else
40
+ @response = response
41
+ end
42
+ }
43
+ }
44
+ end
45
+
46
+ def POST(path, formdata={}, header={})
47
+ Net::HTTP.start(@host, @port) { |http|
48
+ user = header.delete(:user)
49
+ passwd = header.delete(:passwd)
50
+
51
+ post = Net::HTTP::Post.new(path, header)
52
+ post.form_data = formdata
53
+ post.basic_auth user, passwd if user && passwd
54
+ http.request(post) { |response|
55
+ @status = response.code.to_i
56
+ @response = YAML.load(response.body)
57
+ }
58
+ }
59
+ end
60
+
61
+ def load_yaml(response)
62
+ begin
63
+ @response = YAML.load(response.body)
64
+ rescue ArgumentError
65
+ @response = nil
66
+ end
67
+ end
68
+ end
69
+ end