soba 0.1.0.pre → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: '052379d3ef962797c865da3c1757c372c6f15b69'
4
- data.tar.gz: 3c5240d046b932c173e33ba8f8162aba18c8adee
3
+ metadata.gz: bf5254ef757d85ce6742c3ae2f6ca79b90edf225
4
+ data.tar.gz: 58c573c317ad913d7d575346ccfcaecb4da04eef
5
5
  SHA512:
6
- metadata.gz: b0a95f068a7d263d24451c3045c4a330a21090d7691bf48a11f3ff2055387a78e114f29b8c439bd471ef6c769d8b577d4ac74dc5f9bd753987f0df47155ae77e
7
- data.tar.gz: 6ea0694d524551fd876f8f70a9937e1b4ad975680708e74eb61a3558558da9dee42c2238dce2d2d8d22e38d0c83cd63cc323d838b23534b10d2e1e9a50543dad
6
+ metadata.gz: 7e7139df4df2af1f60a02f055d15479d935f236b31c41280a62394dab5d18422d5f43850688cadb03166ba191d8026d02cd7655d5ce0f0b5eb50718c08308689
7
+ data.tar.gz: 1bd8204f7a8706890870f306aa9f09f694d83ae5e07417d4dbf3adc8dee23d3957d38bef6d7b521ccac256839f80b20806f7179eca036d90d9525177c0bbb5dc
data/Gemfile.lock CHANGED
@@ -1,16 +1,20 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- soba (0.1.0.pre)
5
- lightio (~> 0.4)
4
+ soba (0.1.0)
5
+ lightio (~> 0.4.3)
6
+ pico_http_parser (~> 0.0)
7
+ rack
6
8
 
7
9
  GEM
8
10
  remote: https://rubygems.org/
9
11
  specs:
10
12
  diff-lcs (1.3)
11
- lightio (0.4.2)
13
+ lightio (0.4.3)
12
14
  nio4r (~> 2.2)
13
15
  nio4r (2.2.0)
16
+ pico_http_parser (0.0.4)
17
+ rack (2.0.4)
14
18
  rake (10.5.0)
15
19
  rspec (3.7.0)
16
20
  rspec-core (~> 3.7.0)
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Soba
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/soba`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Soba is an experimental rack server, which build upon green thread.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ Green thread can easily achieve high concurrent(especially for IO-heavy app), and save the costs of native threads.
6
+
7
+ Soba use [LightIO](https://github.com/socketry/lightio) to provision green threads.
6
8
 
7
9
  ## Installation
8
10
 
@@ -22,7 +24,31 @@ Or install it yourself as:
22
24
 
23
25
  ## Usage
24
26
 
25
- TODO: Write usage instructions here
27
+ This is a example of soba with rack, put these content into `config.ru`, then run `rackup` to start soba.
28
+
29
+ Notice:
30
+ > 1. Even in rails, soba should be started through `rackup`, so we can apply monkey patch before other code loaded.
31
+ > 2. In `config.ru`, the commented first line is rackup arguments.
32
+
33
+ ``` ruby
34
+ #\ -s soba -O Port=3000
35
+
36
+ # apply green threads monkey patch before application load
37
+ require 'soba/monkey'
38
+ Soba::Monkey.patch!
39
+
40
+ use Rack::Reloader, 0
41
+ use Rack::ContentLength
42
+
43
+ app = proc do |env|
44
+ [ 200, {'Content-Type' => 'text/plain'}, ["你好,世界"] ]
45
+ end
46
+
47
+ run app
48
+
49
+ ```
50
+
51
+ See [examples](/examples/)
26
52
 
27
53
  ## Development
28
54
 
@@ -0,0 +1,69 @@
1
+ ## Benchmark
2
+
3
+ ### Rack: hello world
4
+
5
+ Create a aws `t2.xlarge` machine.
6
+
7
+ Use `rackup` to start server.
8
+
9
+ ```ruby
10
+ #\ -s soba -q -O Port=3000
11
+
12
+ require 'soba/server'
13
+
14
+ # apply green threads monkey patch before application load
15
+ require 'soba/monkey'
16
+ Soba::Monkey.patch!
17
+
18
+ app = proc do |env|
19
+ [ 200, {'Content-Type' => 'text/plain'}, ["你好,世界"] ]
20
+ end
21
+
22
+ run app
23
+ ```
24
+
25
+ run `wrk -t12 -c400 -d30s http://127.0.0.1:3000/`
26
+
27
+ ```
28
+ Soba:
29
+ Running 30s test @ http://127.0.0.1:3000/
30
+ 12 threads and 400 connections
31
+ Thread Stats Avg Stdev Max +/- Stdev
32
+ Latency 62.35ms 18.20ms 467.83ms 98.53%
33
+ Req/Sec 185.17 77.31 660.00 77.91%
34
+ 65904 requests in 30.06s, 6.10MB read
35
+ Requests/sec: 2192.15
36
+ Transfer/sec: 207.65KB
37
+
38
+ Falcon:
39
+ Running 30s test @ http://127.0.0.1:3000/
40
+ 12 threads and 400 connections
41
+ Thread Stats Avg Stdev Max +/- Stdev
42
+ Latency 115.32ms 28.54ms 1.09s 96.18%
43
+ Req/Sec 284.39 83.47 666.00 83.16%
44
+ 102006 requests in 30.04s, 9.24MB read
45
+ Socket errors: connect 0, read 0, write 0, timeout 1
46
+ Requests/sec: 3395.81
47
+ Transfer/sec: 315.04KB
48
+
49
+ Puma:
50
+ Running 30s test @ http://127.0.0.1:3000/
51
+ 12 threads and 400 connections
52
+ Thread Stats Avg Stdev Max +/- Stdev
53
+ Latency 7.34ms 10.55ms 104.94ms 86.40%
54
+ Req/Sec 1.10k 441.58 2.00k 56.57%
55
+ 65568 requests in 30.10s, 6.13MB read
56
+ Requests/sec: 2178.59
57
+ Transfer/sec: 208.50KB
58
+
59
+ Thin:
60
+ Running 30s test @ http://127.0.0.1:3000/
61
+ 12 threads and 400 connections
62
+ Thread Stats Avg Stdev Max +/- Stdev
63
+ Latency 46.76ms 81.43ms 1.11s 98.09%
64
+ Req/Sec 322.11 239.56 0.97k 64.13%
65
+ 29674 requests in 30.07s, 3.71MB read
66
+ Socket errors: connect 359, read 0, write 0, timeout 10
67
+ Requests/sec: 986.78
68
+ Transfer/sec: 126.24KB
69
+ ```
@@ -0,0 +1,14 @@
1
+ #\ -s soba -O Port=3000
2
+
3
+ # apply green threads monkey patch before application load
4
+ require 'soba/monkey'
5
+ Soba::Monkey.patch!
6
+
7
+ use Rack::Reloader, 0
8
+ use Rack::ContentLength
9
+
10
+ app = proc do |env|
11
+ [ 200, {'Content-Type' => 'text/plain'}, ["你好,世界"] ]
12
+ end
13
+
14
+ run app
@@ -0,0 +1,40 @@
1
+ require 'rack/handler'
2
+ require 'soba/server'
3
+
4
+ module Rack
5
+ module Handler
6
+ module Soba
7
+ DEFAULT_OPTIONS = {
8
+ :debug => false,
9
+ }
10
+
11
+
12
+ def self.run(app, options = {})
13
+ options = DEFAULT_OPTIONS.merge(options)
14
+
15
+ host, port = options[:Host], options[:Port]
16
+
17
+ server = ::Soba::Server.new(app, host: host, port: port, **options)
18
+
19
+ yield server if block_given?
20
+ begin
21
+ server.run
22
+ rescue Interrupt
23
+ puts "* Stopping..."
24
+ # server.stop
25
+ puts "* Cool!"
26
+ end
27
+ end
28
+
29
+ def self.valid_options
30
+ {
31
+ "Host=HOST" => "Hostname to listen on (default: localhost)",
32
+ "Port=PORT" => "Port to listen on (default: 8080)",
33
+ "debug=false" => "Enable debug output (default: false)",
34
+ }
35
+ end
36
+ end
37
+
38
+ register :soba, Soba
39
+ end
40
+ end
data/lib/soba.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require "soba/version"
2
- require "soba/server"
3
2
 
4
3
  module Soba
5
4
  # Your code goes here...
data/lib/soba/cli.rb ADDED
@@ -0,0 +1,11 @@
1
+ require_relative 'server'
2
+
3
+ module Soba
4
+ class CLI
5
+ def run
6
+ host, port = ARGV
7
+ app = lambda {|env| [200, {'Host' => 'securet'}, "nihaoooo"]}
8
+ Soba::Server.new(app, host, port).run
9
+ end
10
+ end
11
+ end
data/lib/soba/const.rb ADDED
@@ -0,0 +1,217 @@
1
+ #encoding: utf-8
2
+ # COPY FROM Puma server
3
+ require_relative 'version'
4
+ module Soba
5
+ # Every standard HTTP code mapped to the appropriate message. These are
6
+ # used so frequently that they are placed directly in Puma for easy
7
+ # access rather than Puma::Const itself.
8
+
9
+ # Every standard HTTP code mapped to the appropriate message.
10
+ # Generated with:
11
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
12
+ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
13
+ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
14
+ HTTP_STATUS_CODES = {
15
+ 100 => 'Continue',
16
+ 101 => 'Switching Protocols',
17
+ 102 => 'Processing',
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
+ 207 => 'Multi-Status',
26
+ 208 => 'Already Reported',
27
+ 226 => 'IM Used',
28
+ 300 => 'Multiple Choices',
29
+ 301 => 'Moved Permanently',
30
+ 302 => 'Found',
31
+ 303 => 'See Other',
32
+ 304 => 'Not Modified',
33
+ 305 => 'Use Proxy',
34
+ 307 => 'Temporary Redirect',
35
+ 308 => 'Permanent Redirect',
36
+ 400 => 'Bad Request',
37
+ 401 => 'Unauthorized',
38
+ 402 => 'Payment Required',
39
+ 403 => 'Forbidden',
40
+ 404 => 'Not Found',
41
+ 405 => 'Method Not Allowed',
42
+ 406 => 'Not Acceptable',
43
+ 407 => 'Proxy Authentication Required',
44
+ 408 => 'Request Timeout',
45
+ 409 => 'Conflict',
46
+ 410 => 'Gone',
47
+ 411 => 'Length Required',
48
+ 412 => 'Precondition Failed',
49
+ 413 => 'Payload Too Large',
50
+ 414 => 'URI Too Long',
51
+ 415 => 'Unsupported Media Type',
52
+ 416 => 'Range Not Satisfiable',
53
+ 417 => 'Expectation Failed',
54
+ 418 => 'I\'m A Teapot',
55
+ 421 => 'Misdirected Request',
56
+ 422 => 'Unprocessable Entity',
57
+ 423 => 'Locked',
58
+ 424 => 'Failed Dependency',
59
+ 426 => 'Upgrade Required',
60
+ 428 => 'Precondition Required',
61
+ 429 => 'Too Many Requests',
62
+ 431 => 'Request Header Fields Too Large',
63
+ 451 => 'Unavailable For Legal Reasons',
64
+ 500 => 'Internal Server Error',
65
+ 501 => 'Not Implemented',
66
+ 502 => 'Bad Gateway',
67
+ 503 => 'Service Unavailable',
68
+ 504 => 'Gateway Timeout',
69
+ 505 => 'HTTP Version Not Supported',
70
+ 506 => 'Variant Also Negotiates',
71
+ 507 => 'Insufficient Storage',
72
+ 508 => 'Loop Detected',
73
+ 510 => 'Not Extended',
74
+ 511 => 'Network Authentication Required'
75
+ }
76
+
77
+ STATUS_WITH_NO_ENTITY_BODY = {
78
+ 204 => true,
79
+ 205 => true,
80
+ 304 => true
81
+ }
82
+
83
+ # Frequently used constants when constructing requests or responses. Many times
84
+ # the constant just refers to a string with the same contents. Using these constants
85
+ # gave about a 3% to 10% performance improvement over using the strings directly.
86
+ #
87
+ # The constants are frozen because Hash#[]= when called with a String key dups
88
+ # the String UNLESS the String is frozen. This saves us therefore 2 object
89
+ # allocations when creating the env hash later.
90
+ #
91
+ # While Puma does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT,
92
+ # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
93
+ # too taxing on performance.
94
+ module Const
95
+
96
+ SOBA_VERSION = VERSION
97
+
98
+ FAST_TRACK_KA_TIMEOUT = 0.2
99
+
100
+ # The default number of seconds for another request within a persistent
101
+ # session.
102
+ PERSISTENT_TIMEOUT = 20
103
+
104
+ # The default number of seconds to wait until we get the first data
105
+ # for the request
106
+ FIRST_DATA_TIMEOUT = 30
107
+
108
+ # How long to wait when getting some write blocking on the socket when
109
+ # sending data back
110
+ WRITE_TIMEOUT = 10
111
+
112
+ # The original URI requested by the client.
113
+ REQUEST_URI = 'REQUEST_URI'.freeze
114
+ REQUEST_PATH = 'REQUEST_PATH'.freeze
115
+ QUERY_STRING = 'QUERY_STRING'.freeze
116
+
117
+ PATH_INFO = 'PATH_INFO'.freeze
118
+
119
+ # Indicate that we couldn't parse the request
120
+ ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
121
+
122
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
123
+ ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{SOBA_VERSION}\r\n\r\nNOT FOUND".freeze
124
+
125
+ # The standard empty 408 response for requests that timed out.
126
+ ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{SOBA_VERSION}\r\n\r\n".freeze
127
+
128
+ CONTENT_LENGTH = "CONTENT_LENGTH".freeze
129
+
130
+ # Indicate that there was an internal error, obviously.
131
+ ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
132
+
133
+ # A common header for indicating the server is too busy. Not used yet.
134
+ ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
135
+
136
+ # The basic max request size we'll try to read.
137
+ CHUNK_SIZE = 16 * 1024
138
+
139
+ # This is the maximum header that is allowed before a client is booted. The parser detects
140
+ # this, but we'd also like to do this as well.
141
+ MAX_HEADER = 1024 * (80 + 32)
142
+
143
+ # Maximum request body size before it is moved out of memory and into a tempfile for reading.
144
+ MAX_BODY = MAX_HEADER
145
+
146
+ REQUEST_METHOD = "REQUEST_METHOD".freeze
147
+ HEAD = "HEAD".freeze
148
+ # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
149
+ LINE_END = "\r\n".freeze
150
+ REMOTE_ADDR = "REMOTE_ADDR".freeze
151
+ HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
152
+
153
+ SERVER_NAME = "SERVER_NAME".freeze
154
+ SERVER_PORT = "SERVER_PORT".freeze
155
+ HTTP_HOST = "HTTP_HOST".freeze
156
+ PORT_80 = "80".freeze
157
+ PORT_443 = "443".freeze
158
+ LOCALHOST = "localhost".freeze
159
+ LOCALHOST_IP = "127.0.0.1".freeze
160
+ LOCALHOST_ADDR = "127.0.0.1:0".freeze
161
+
162
+ SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
163
+ HTTP_11 = "HTTP/1.1".freeze
164
+
165
+ SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
166
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
167
+ CGI_VER = "CGI/1.2".freeze
168
+
169
+ STOP_COMMAND = "?".freeze
170
+ HALT_COMMAND = "!".freeze
171
+ RESTART_COMMAND = "R".freeze
172
+
173
+ RACK_INPUT = "rack.input".freeze
174
+ RACK_URL_SCHEME = "rack.url_scheme".freeze
175
+ RACK_AFTER_REPLY = "rack.after_reply".freeze
176
+
177
+ HTTP = "http".freeze
178
+ HTTPS = "https".freeze
179
+
180
+ HTTPS_KEY = "HTTPS".freeze
181
+
182
+ HTTP_VERSION = "HTTP_VERSION".freeze
183
+ HTTP_CONNECTION = "HTTP_CONNECTION".freeze
184
+ HTTP_EXPECT = "HTTP_EXPECT".freeze
185
+ CONTINUE = "100-continue".freeze
186
+
187
+ HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze
188
+ HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
189
+ HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
190
+
191
+ CLOSE = "close".freeze
192
+ KEEP_ALIVE = "keep-alive".freeze
193
+
194
+ CONTENT_LENGTH2 = "content-length".freeze
195
+ CONTENT_LENGTH_S = "Content-Length: ".freeze
196
+ TRANSFER_ENCODING = "transfer-encoding".freeze
197
+ TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze
198
+
199
+ CONNECTION_CLOSE = "Connection: close\r\n".freeze
200
+ CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
201
+
202
+ TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
203
+ CLOSE_CHUNKED = "0\r\n\r\n".freeze
204
+
205
+ CHUNKED = "chunked".freeze
206
+
207
+ COLON = ": ".freeze
208
+
209
+ NEWLINE = "\n".freeze
210
+
211
+ HIJACK_P = "rack.hijack?".freeze
212
+ HIJACK = "rack.hijack".freeze
213
+ HIJACK_IO = "rack.hijack_io".freeze
214
+
215
+ EARLY_HINTS = "rack.early_hints".freeze
216
+ end
217
+ end
@@ -0,0 +1,16 @@
1
+ require 'lightio'
2
+ module Soba
3
+ module Monkey
4
+ class << self
5
+ def patch!
6
+ origin_verbose = $VERBOSE
7
+ $VERBOSE = nil
8
+ LightIO::Monkey.patch_all! unless LightIO::Monkey.patched?(IO)
9
+ ensure
10
+ $VERBOSE = origin_verbose
11
+ end
12
+
13
+ alias patch_all! patch!
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,61 @@
1
+ require 'pico_http_parser'
2
+
3
+ module Soba
4
+ class Parser
5
+ CRLF = "\r\n".freeze
6
+
7
+ class InvalidRequest
8
+ end
9
+
10
+ attr_reader :socket
11
+
12
+ def initialize(socket)
13
+ @socket = socket
14
+ @headers = nil
15
+ @body = nil
16
+ end
17
+
18
+ def headers
19
+ @headers ||= begin
20
+ request_body = ""
21
+ while (line = socket.readline) != CRLF
22
+ request_body << line
23
+ end
24
+ request_body << CRLF
25
+ debug request_body
26
+ ret = PicoHTTPParser.parse_http_request(request_body, @headers ||= {})
27
+ debug ret
28
+ raise InvalidRequest, "ret: #{ret}" if ret < 0
29
+ @headers
30
+ end
31
+ end
32
+
33
+ alias env headers
34
+
35
+ # http or https
36
+ def request_schema
37
+ @request_schema ||= server_protocol.split("/").first.downcase
38
+ end
39
+
40
+ def content_length
41
+ headers["CONTENT_LENGTH"]&.to_i
42
+ end
43
+
44
+ def body
45
+ @body ||= begin
46
+ StringIO.new(content_length ? socket.read(content_length) : '').binmode
47
+ end
48
+ end
49
+
50
+ %w{SERVER_PROTOCOL}.each do |header|
51
+ define_method header.downcase do
52
+ headers[header]
53
+ end
54
+ end
55
+
56
+ private
57
+ def debug(*args)
58
+ STDERR.puts(args) if ENV["SOBA_DEBUG"] == '1'
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,7 @@
1
+ require 'rack/handler/soba'
2
+
3
+ module Rack::Handler
4
+ def self.default(options = {})
5
+ Rack::Handler::Soba
6
+ end
7
+ end
data/lib/soba/server.rb CHANGED
@@ -1,26 +1,149 @@
1
1
  require 'lightio'
2
+ require 'logger'
3
+ require_relative 'parser'
4
+ require_relative 'const'
2
5
 
3
6
  module Soba
4
7
  class Server
5
- def initialize(host, port)
6
- @server = LightIO::TCPServer.new(host, port)
8
+ attr_reader :logger, :host, :port, :app
9
+
10
+ def error_stream
11
+ STDERR
12
+ end
13
+
14
+ include Const
15
+
16
+ def initialize(app, host:, port:, **options)
17
+ @app = app
18
+ @host, @port = host, port
19
+ @server = nil
20
+ @logger = Logger.new(STDOUT, level: is_true?(options[:debug]) ? Logger::DEBUG : Logger::INFO)
21
+ @options = options
7
22
  end
8
23
 
9
24
  def run
25
+ setup
10
26
  while (socket = @server.accept)
11
27
  _, port, host = socket.peeraddr
12
- puts "accept connection from #{host}:#{port}"
28
+ logger.debug "accept connection from #{host}:#{port}"
13
29
 
14
30
  LightIO::Beam.new(socket) do |socket|
15
- socket << "HTTP/1.1 200 OK\n"
16
- socket << "Content-Type: text/html\n"
17
- socket << "Content-Length: 2\n"
18
- socket << "Accept-Ranges: bytes\n"
19
- socket << "\n"
20
- socket << "OK"
21
- socket.close
31
+ begin
32
+ process_request(socket) until socket.closed?
33
+ rescue StandardError => e
34
+ logger.info("Exception: #{e}")
35
+ raise
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def rack_env
42
+ @rack_env ||= {
43
+ "rack.version" => Rack::VERSION,
44
+ "rack.multithread" => true,
45
+ "rack.multiprocess" => false,
46
+ "rack.run_once" => false,
47
+ "rack.hijack?" => false,
48
+ "rack.hijack" => nil,
49
+ "rack.hijack_io" => nil,
50
+ "rack.logger" => logger,
51
+ "SERVER_PORT" => port.to_s,
52
+ }
53
+ end
54
+
55
+ def process_request(socket)
56
+ parser = Parser.new(socket)
57
+ env = parser.env.merge(rack_env)
58
+ env["rack.url_scheme"] = parser.request_schema
59
+ env["rack.input"] = parser.body
60
+ env["rack.errors"] = error_stream
61
+ env["SERVER_NAME"] = env[HTTP_HOST].split(":")[0]
62
+
63
+ keep_alive = env[HTTP_CONNECTION].to_s.downcase == KEEP_ALIVE
64
+
65
+ begin
66
+ status, headers, res_body = app.call(env)
67
+ logger.debug("response: #{status} #{headers.inspect} #{res_body.inspect}")
68
+ rescue Exception => e
69
+ status = 500
70
+ headers = {'Content-Type' => 'text/plain'}
71
+ logger.error("Internal Server Error: #{e}:\n#{e.backtrace.join("\n")}")
72
+ end
73
+
74
+ nobody = parser.env[REQUEST_METHOD] == HEAD || STATUS_WITH_NO_ENTITY_BODY[status]
75
+
76
+ outbuf = ''
77
+ status_line = "#{parser.server_protocol} #{status} #{status_text(status)}\n"
78
+ outbuf << status_line
79
+
80
+ set_content_length = false
81
+ headers.each do |header, vs|
82
+ case header.downcase
83
+ when CONTENT_LENGTH2, CONTENT_LENGTH
84
+ set_content_length = true
22
85
  end
86
+ outbuf << "#{header}#{COLON}#{vs}#{LINE_END}"
23
87
  end
88
+
89
+ chunked = !set_content_length
90
+ outbuf << TRANSFER_ENCODING_CHUNKED if chunked
91
+
92
+ connection_header = keep_alive ? CONNECTION_KEEP_ALIVE : CONNECTION_CLOSE
93
+ outbuf << connection_header
94
+ outbuf << "\n"
95
+
96
+ if nobody
97
+ socket << outbuf
98
+ socket.flush
99
+ return
100
+ end
101
+
102
+ res_body.each do |part|
103
+ if chunked
104
+ next if part.bytesize.zero?
105
+ outbuf << part.bytesize.to_s(16)
106
+ outbuf << LINE_END
107
+ outbuf << part
108
+ outbuf << LINE_END
109
+ else
110
+ outbuf << part
111
+ end
112
+ end
113
+
114
+ if chunked
115
+ outbuf << CLOSE_CHUNKED
116
+ end
117
+ # write is very slow, don't know why
118
+ socket << outbuf
119
+ socket.flush
120
+ res_body.close if res_body.respond_to?(:close)
121
+ ensure
122
+ socket.close unless socket.closed? || keep_alive
123
+ end
124
+
125
+ private
126
+ def status_text(status)
127
+ HTTP_STATUS_CODES[status.to_i]
128
+ end
129
+
130
+ def setup
131
+ monkey_patch = LightIO::Monkey.patched?(IO)
132
+ logger.info "Soba #{Soba::VERSION}"
133
+ logger.info "ruby #{RUBY_VERSION}"
134
+ if monkey_patch
135
+ logger.info "Run in Green thread(monkey patch) mode, engine: #{NIO.engine}, see https://github.com/socketry/lightio"
136
+ logger.info "Current backend: #{LightIO::IOloop.current.backend}, available backends: #{NIO::Selector.backends} (set `LIGHTIO_BACKEND` env to choose)"
137
+ else
138
+ logger.info "Run in normal mode"
139
+ end
140
+ logger.info "Server start listen #{host}:#{port}"
141
+
142
+ @server = LightIO::TCPServer.new(host, port)
143
+ end
144
+
145
+ def is_true?(v)
146
+ %w{true on 1}.include?(v.to_s)
24
147
  end
25
148
  end
26
- end
149
+ end
data/lib/soba/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Soba
2
- VERSION = "0.1.0.pre"
2
+ VERSION = "0.1.0"
3
3
  end
data/soba.gemspec CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Jiang Jinyang"]
10
10
  spec.email = ["jjyruby@gmail.com"]
11
11
 
12
- spec.summary = %q{A tiny web server.}
13
- spec.description = %q{A tiny web server, build upon green threads.}
12
+ spec.summary = %q{Soba is an experience rack server, which build upon green thread.}
13
+ spec.description = %q{Soba use [LightIO](https://github.com/socketry/lightio) to provision green threads.}
14
14
  spec.homepage = "https://github.com/jjyr/soba"
15
15
  spec.license = "MIT"
16
16
 
@@ -21,7 +21,9 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_dependency "lightio", "~> 0.4"
24
+ spec.add_dependency "lightio", "~> 0.4.3"
25
+ spec.add_dependency "pico_http_parser", "~> 0.0"
26
+ spec.add_dependency "rack"
25
27
  spec.add_development_dependency "bundler", "~> 1.16"
26
28
  spec.add_development_dependency "rake", "~> 10.0"
27
29
  spec.add_development_dependency "rspec", "~> 3.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jiang Jinyang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-24 00:00:00.000000000 Z
11
+ date: 2018-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lightio
@@ -16,14 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.4'
19
+ version: 0.4.3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.4'
26
+ version: 0.4.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: pico_http_parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: bundler
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -66,7 +94,8 @@ dependencies:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
96
  version: '3.0'
69
- description: A tiny web server, build upon green threads.
97
+ description: Soba use [LightIO](https://github.com/socketry/lightio) to provision
98
+ green threads.
70
99
  email:
71
100
  - jjyruby@gmail.com
72
101
  executables: []
@@ -82,10 +111,17 @@ files:
82
111
  - LICENSE.txt
83
112
  - README.md
84
113
  - Rakefile
114
+ - benchmark/README.md
85
115
  - bin/console
86
116
  - bin/setup
87
- - bin/soba
117
+ - examples/config.ru
118
+ - lib/rack/handler/soba.rb
88
119
  - lib/soba.rb
120
+ - lib/soba/cli.rb
121
+ - lib/soba/const.rb
122
+ - lib/soba/monkey.rb
123
+ - lib/soba/parser.rb
124
+ - lib/soba/rack_default.rb
89
125
  - lib/soba/server.rb
90
126
  - lib/soba/version.rb
91
127
  - soba.gemspec
@@ -104,13 +140,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
104
140
  version: '0'
105
141
  required_rubygems_version: !ruby/object:Gem::Requirement
106
142
  requirements:
107
- - - ">"
143
+ - - ">="
108
144
  - !ruby/object:Gem::Version
109
- version: 1.3.1
145
+ version: '0'
110
146
  requirements: []
111
147
  rubyforge_project:
112
148
  rubygems_version: 2.6.14
113
149
  signing_key:
114
150
  specification_version: 4
115
- summary: A tiny web server.
151
+ summary: Soba is an experience rack server, which build upon green thread.
116
152
  test_files: []
data/bin/soba DELETED
@@ -1,5 +0,0 @@
1
- require "bundler/setup"
2
- require "soba"
3
-
4
- host, port = ARGV
5
- Soba::Server.new(host, port).run