rosendo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +16 -0
- data/README.md +87 -0
- data/example.rb +43 -0
- data/lib/rosendo.rb +8 -0
- data/lib/rosendo/app.rb +35 -0
- data/lib/rosendo/dsl.rb +24 -0
- data/lib/rosendo/request.rb +27 -0
- data/lib/rosendo/response.rb +34 -0
- data/lib/rosendo/routes.rb +86 -0
- data/lib/rosendo/server.rb +37 -0
- data/test/integration/basic_test.rb +134 -0
- data/test/test_helper.rb +60 -0
- data/test/unit/sample_test.rb +7 -0
- metadata +79 -0
data/LICENSE
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Copyright (c) 2013 Sergio Gil
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
4
|
+
associated documentation files (the "Software"), to deal in the Software without restriction,
|
5
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
6
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
7
|
+
furnished to do so, subject to the following conditions:
|
8
|
+
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
10
|
+
portions of the Software.
|
11
|
+
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
13
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
14
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
15
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
16
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# Rosendo [![Build Status](https://travis-ci.org/porras/rosendo.png)](https://travis-ci.org/porras/rosendo)
|
2
|
+
|
3
|
+
Rosendo is a minimalistic and naive [Sinatra](http://sinatrarb.com) reimplementation, without any
|
4
|
+
dependencies other than the ruby socket library. It's a learning exercise on the HTTP specs, web
|
5
|
+
servers, and how not to write software. It contains a (stupidly simple) HTTP server, a (rather
|
6
|
+
incomplete) HTTP parser and a (really oversimplified) DSL.
|
7
|
+
|
8
|
+
**Rosendo is not intended for any production use. Specifically, *it's not intended as a Sinatra
|
9
|
+
replacement*. It has much less features, for sure much more bugs, and probably much worse
|
10
|
+
performance. It's just a learning exercise.**
|
11
|
+
|
12
|
+
This ongoing exercise is a game, whose rules (invented by myself) consist in reimplementing a web
|
13
|
+
framework (Sinatra is far better for this purpose than my other favorite framework Rails, for
|
14
|
+
obvious reasons) from scratch, using just Ruby. I wanted to prevent myself from using anything from
|
15
|
+
the standard library, but probably making this without the socket library would be too much, so
|
16
|
+
that's the only allowed exception. Still, no webrick, no erb, no nothing, and obviously no gems.
|
17
|
+
|
18
|
+
The rule doesn't apply to the tests though (I'm using minitest and Net::HTTP).
|
19
|
+
|
20
|
+
The purpose of the game/exercise comes from a reflection about how much software we usually depend
|
21
|
+
on when writing our applications (just run `bundle show` in your last Rails app if you don't believe
|
22
|
+
me) and is double:
|
23
|
+
|
24
|
+
1) Remember that the software we take for granted and like to whine about some times, it's actually
|
25
|
+
great software we should thank for every minute we're using it. Trying to live without it for some
|
26
|
+
time is a great way to achieve this.
|
27
|
+
|
28
|
+
2) At the same time, demystifying it. Not everybody can write such good web servers like the ones we
|
29
|
+
use everyday, but everybody can write the simplest one. And understanding a bit how they work is a
|
30
|
+
great thing, even if you're not obviously going to write a web server every time you want to write a
|
31
|
+
web app. The same is true for every other piece of the stack.
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
Install it via Rubygems or Bundler, require it, and pretend it's Sinatra:
|
36
|
+
|
37
|
+
require 'rosendo'
|
38
|
+
|
39
|
+
class MyApp < Rosendo::App
|
40
|
+
get "/" do
|
41
|
+
"Hello, World!"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
MyApp.run!(port: 2000)
|
46
|
+
|
47
|
+
## Features
|
48
|
+
|
49
|
+
### Sinatra features that Rosendo supports
|
50
|
+
|
51
|
+
* Route mapping methods (`get`, `post`, ...)
|
52
|
+
* Headers reading and setting
|
53
|
+
* Basic params in URL (`/hello/:name`)
|
54
|
+
* HTTP status code
|
55
|
+
* Redirects
|
56
|
+
* Params in query string
|
57
|
+
|
58
|
+
### Sinatra features that Rosendo plans to support
|
59
|
+
|
60
|
+
* Form params in request body
|
61
|
+
* Templates
|
62
|
+
|
63
|
+
### Sinatra features that Rosendo doesn't plan to support (for the moment)
|
64
|
+
|
65
|
+
* Advanced route patterns (*, regular expressions, conditions, ...)
|
66
|
+
* Filters
|
67
|
+
* Helpers
|
68
|
+
* Variables (`set` method)
|
69
|
+
* Cookies
|
70
|
+
* Sessions
|
71
|
+
* Config blocks
|
72
|
+
* Rack compliance
|
73
|
+
* Streaming
|
74
|
+
* Logging
|
75
|
+
* *Classic* mode
|
76
|
+
* Static files
|
77
|
+
|
78
|
+
See [`example.rb`](https://github.com/porras/rosendo/blob/master/example.rb) for some supported things.
|
79
|
+
|
80
|
+
[Rosendo](http://en.wikipedia.org/wiki/Rosendo_Mercado) is also the name of the most charismatic
|
81
|
+
Spanish rock singer and songwriter ever.
|
82
|
+
|
83
|
+
[![Rosendo Mercado](http://upload.wikimedia.org/wikipedia/commons/thumb/5/5a/Rosendo_-_11.jpg/320px-Rosendo_-_11.jpg)](http://en.wikipedia.org/wiki/Rosendo_Mercado)
|
84
|
+
|
85
|
+
## License
|
86
|
+
|
87
|
+
Released under the [MIT license](https://github.com/porras/rosendo/blob/master/LICENSE).
|
data/example.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require './lib/rosendo'
|
2
|
+
|
3
|
+
class Example < Rosendo::App
|
4
|
+
get '/' do
|
5
|
+
'Hola mundo'
|
6
|
+
end
|
7
|
+
|
8
|
+
get '/wadus' do
|
9
|
+
'Hola wadus'
|
10
|
+
end
|
11
|
+
|
12
|
+
get '/hello/:name/:surname' do
|
13
|
+
"#{params[:surname]}, #{params[:name]}"
|
14
|
+
end
|
15
|
+
|
16
|
+
get '/berlin' do
|
17
|
+
'Hola Berlin'
|
18
|
+
end
|
19
|
+
|
20
|
+
get '/headers' do
|
21
|
+
headers 'X-Wadus' => 'Wadus!!'
|
22
|
+
"Received headers: #{request.env.inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
get '/status/:code' do
|
26
|
+
status params[:code].to_i
|
27
|
+
"#{params[:code]} Invented Status"
|
28
|
+
end
|
29
|
+
|
30
|
+
get '/redirect' do
|
31
|
+
redirect '/'
|
32
|
+
end
|
33
|
+
|
34
|
+
get '/params' do
|
35
|
+
params.inspect
|
36
|
+
end
|
37
|
+
|
38
|
+
get '/exception' do
|
39
|
+
raise 'Catacrocker'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Example.run!(port: 2000)
|
data/lib/rosendo.rb
ADDED
data/lib/rosendo/app.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Rosendo
|
2
|
+
class App
|
3
|
+
class << self
|
4
|
+
%w{GET POST PUT DELETE}.each do |method|
|
5
|
+
define_method method.downcase do |*args, &block|
|
6
|
+
routes.add(method, *args, &block)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def run!(options = {})
|
11
|
+
Server.new(self, options).start
|
12
|
+
end
|
13
|
+
|
14
|
+
def process(request, response)
|
15
|
+
if route = routes.for(request)
|
16
|
+
response.body = begin
|
17
|
+
route.call(request, response)
|
18
|
+
rescue Exception => e
|
19
|
+
response.status = 500
|
20
|
+
e.inspect + "\n\n" + e.backtrace.join("\n")
|
21
|
+
end
|
22
|
+
else
|
23
|
+
response.status = 404
|
24
|
+
response.body = "404 Not Found"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def routes
|
31
|
+
@routes ||= Routes.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/rosendo/dsl.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rosendo
|
2
|
+
class DSL
|
3
|
+
attr_reader :request, :params
|
4
|
+
def initialize(request, response, params)
|
5
|
+
@request = request
|
6
|
+
@response = response
|
7
|
+
@params = params
|
8
|
+
end
|
9
|
+
|
10
|
+
def headers(extra = {})
|
11
|
+
@response.headers.merge!(extra)
|
12
|
+
end
|
13
|
+
|
14
|
+
def status(code)
|
15
|
+
@response.status = code
|
16
|
+
end
|
17
|
+
|
18
|
+
def redirect(url, code = 302, content = "")
|
19
|
+
status code
|
20
|
+
headers 'Location' => url
|
21
|
+
content
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rosendo
|
2
|
+
class Request
|
3
|
+
attr_reader :method, :url, :headers
|
4
|
+
alias_method :env, :headers
|
5
|
+
def initialize(io)
|
6
|
+
@io = io
|
7
|
+
@method, @url = @io.gets.match(%r{(GET|POST|PUT|DELETE)\s(.+)\sHTTP/1\.1})[1..2]
|
8
|
+
@headers = read_headers
|
9
|
+
end
|
10
|
+
|
11
|
+
def params
|
12
|
+
{}
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def read_headers
|
18
|
+
{}.tap do |h|
|
19
|
+
while line = @io.gets.chomp
|
20
|
+
break if line.empty?
|
21
|
+
m = line.match(%r{^([\w\-]+):\s(.+)$})
|
22
|
+
h[m[1]] = m[2]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Rosendo
|
2
|
+
class Response
|
3
|
+
attr_accessor :status, :headers, :body
|
4
|
+
def initialize(io)
|
5
|
+
@io = io
|
6
|
+
@headers = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def status
|
10
|
+
@status || 200
|
11
|
+
end
|
12
|
+
|
13
|
+
def body
|
14
|
+
@body || ''
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond
|
18
|
+
@io.puts status_line
|
19
|
+
@io.puts header_lines
|
20
|
+
@io.puts
|
21
|
+
@io.write body # write instead of puts for no extra newline
|
22
|
+
@io.close
|
23
|
+
end
|
24
|
+
|
25
|
+
def status_line
|
26
|
+
"HTTP/1.1 #{status}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def header_lines
|
30
|
+
{'Content-Length' => body.size}.merge(@headers).map { |k, v| "#{k}: #{v}" }.join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Rosendo
|
2
|
+
class Routes
|
3
|
+
def initialize
|
4
|
+
@routes = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def <<(route)
|
8
|
+
@routes << route
|
9
|
+
end
|
10
|
+
|
11
|
+
def for(request)
|
12
|
+
@routes.detect { |route| route.matches?(request) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(method, path, &block)
|
16
|
+
self << Route.new(method, path, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
class Route
|
20
|
+
attr_reader :method, :path
|
21
|
+
def initialize(method, path, &block)
|
22
|
+
@method = method
|
23
|
+
@path = Path.new(path)
|
24
|
+
@block = block
|
25
|
+
end
|
26
|
+
|
27
|
+
def matches?(request)
|
28
|
+
method == request.method &&
|
29
|
+
path.matches?(request.url)
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(request, response)
|
33
|
+
DSL.new(request, response, path.params(request.url)).instance_eval(&@block)
|
34
|
+
end
|
35
|
+
|
36
|
+
class Path
|
37
|
+
def initialize(path)
|
38
|
+
@path = path
|
39
|
+
@regexp, @keys = parse
|
40
|
+
end
|
41
|
+
|
42
|
+
def matches?(url)
|
43
|
+
url = URL.new(url)
|
44
|
+
url.path =~ @regexp
|
45
|
+
end
|
46
|
+
|
47
|
+
def params(url)
|
48
|
+
url = URL.new(url)
|
49
|
+
match = url.path.match(@regexp)
|
50
|
+
{}.tap do |params|
|
51
|
+
@keys.each_with_index do |key, i|
|
52
|
+
params[key] = match[i + 1]
|
53
|
+
end
|
54
|
+
end.merge(url.query_params)
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse
|
58
|
+
keys = []
|
59
|
+
pattern = @path.gsub(/:(\w+)/) do |match|
|
60
|
+
keys << $1.to_sym
|
61
|
+
'(\w+)'
|
62
|
+
end
|
63
|
+
[Regexp.new("^#{pattern}$"), keys]
|
64
|
+
end
|
65
|
+
|
66
|
+
class URL
|
67
|
+
attr_reader :url, :path, :query
|
68
|
+
def initialize(url)
|
69
|
+
@url = url
|
70
|
+
@path, @query = url.split('?')
|
71
|
+
end
|
72
|
+
|
73
|
+
def query_params
|
74
|
+
return {} unless query
|
75
|
+
{}.tap do |params|
|
76
|
+
query.split('&').each do |pair|
|
77
|
+
k, v = pair.split('=')
|
78
|
+
params[k.to_sym] = v
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Rosendo
|
4
|
+
class Server
|
5
|
+
class Stop < Exception; end
|
6
|
+
|
7
|
+
attr_reader :port, :out
|
8
|
+
def initialize(app, args = {})
|
9
|
+
@app = app
|
10
|
+
@port = args[:port] || '2000'
|
11
|
+
@out = args[:out] || STDOUT
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
out.puts "== Rosendo is rocking the stage on #{port}"
|
16
|
+
out.puts ">> Listening on 0.0.0.0:#{port}, CTRL+C to stop"
|
17
|
+
|
18
|
+
loop do
|
19
|
+
begin
|
20
|
+
client = server.accept
|
21
|
+
request = Request.new(client)
|
22
|
+
response = Response.new(client)
|
23
|
+
@app.process(request, response)
|
24
|
+
response.respond
|
25
|
+
out.puts "#{request.method} #{request.url} #{response.status} #{response.body.size}"
|
26
|
+
rescue Stop
|
27
|
+
server.close
|
28
|
+
out.puts "== Rosendo has left the building (everybody goes crazy)"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def server
|
34
|
+
@server ||= TCPServer.new(@port)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rosendo'
|
3
|
+
|
4
|
+
class BasicTest < IntegrationTest
|
5
|
+
def test_hello_world
|
6
|
+
app do
|
7
|
+
get('/') { 'Hello, World!' }
|
8
|
+
end
|
9
|
+
|
10
|
+
get '/'
|
11
|
+
|
12
|
+
assert_equal(200, response.status)
|
13
|
+
assert_equal('Hello, World!', response.body)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_simple_route
|
17
|
+
app do
|
18
|
+
get('/wadus') { 'Wadus' }
|
19
|
+
end
|
20
|
+
|
21
|
+
get '/wadus'
|
22
|
+
|
23
|
+
assert_equal(200, response.status)
|
24
|
+
assert_equal('Wadus', response.body)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_not_found
|
28
|
+
app do
|
29
|
+
get('/') { 'Hello' }
|
30
|
+
end
|
31
|
+
|
32
|
+
get '/foo'
|
33
|
+
|
34
|
+
assert_equal(404, response.status)
|
35
|
+
assert_equal('404 Not Found', response.body)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_content_size
|
39
|
+
app do
|
40
|
+
get('/') { 'a' * rand(1000) }
|
41
|
+
end
|
42
|
+
|
43
|
+
get '/'
|
44
|
+
|
45
|
+
assert_equal(response.body.size, response.headers['content-length'][0].to_i)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_params_in_url
|
49
|
+
app do
|
50
|
+
get('/hello/:name/:surname') { "Hola, #{params[:name].capitalize} #{params[:surname].capitalize}"}
|
51
|
+
end
|
52
|
+
|
53
|
+
get '/hello/rosendo/mercado'
|
54
|
+
|
55
|
+
assert_equal(200, response.status)
|
56
|
+
assert_equal('Hola, Rosendo Mercado', response.body)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_read_headers
|
60
|
+
app do
|
61
|
+
get('/headers') { request.env['X-Wadus'] }
|
62
|
+
end
|
63
|
+
|
64
|
+
get '/headers', 'X-Wadus' => 'Wadus'
|
65
|
+
|
66
|
+
assert_equal(200, response.status)
|
67
|
+
assert_equal('Wadus', response.body)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_set_headers
|
71
|
+
app do
|
72
|
+
get('/headers') do
|
73
|
+
headers('X-Wadus' => 'Wadus')
|
74
|
+
'Header set'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
get '/headers'
|
79
|
+
|
80
|
+
assert_equal(200, response.status)
|
81
|
+
assert_equal('Header set', response.body)
|
82
|
+
assert_equal(['Wadus'], response.headers['x-wadus']) # header names are case insensitive per RFC,
|
83
|
+
# and Net::HTTP seems to implement this simply
|
84
|
+
# downcasing them. Also, it returns a list
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_status_code
|
88
|
+
app do
|
89
|
+
get('/status/:code') do
|
90
|
+
status params[:code].to_i
|
91
|
+
"#{params[:code]} Invented Status"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
get '/status/234'
|
96
|
+
|
97
|
+
assert_equal(234, response.status)
|
98
|
+
assert_equal('234 Invented Status', response.body)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_redirect
|
102
|
+
app do
|
103
|
+
get('/') { redirect '/home' }
|
104
|
+
end
|
105
|
+
|
106
|
+
get '/'
|
107
|
+
|
108
|
+
assert_equal(302, response.status)
|
109
|
+
assert_equal(['/home'], response.headers['location'])
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_params
|
113
|
+
app do
|
114
|
+
get('/params') { "a: #{params[:a]}; b: #{params[:b]}" }
|
115
|
+
end
|
116
|
+
|
117
|
+
get '/params?a=AAA&b=BBB'
|
118
|
+
|
119
|
+
assert_equal(200, response.status)
|
120
|
+
assert_equal("a: AAA; b: BBB", response.body)
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_exception
|
124
|
+
app do
|
125
|
+
get('/exception') { raise 'Catacrocker' }
|
126
|
+
end
|
127
|
+
|
128
|
+
get '/exception'
|
129
|
+
|
130
|
+
assert_equal(500, response.status)
|
131
|
+
assert_match('Catacrocker', response.body)
|
132
|
+
assert_match(__FILE__, response.body)
|
133
|
+
end
|
134
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'minitest/unit'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module TestHelper
|
7
|
+
module IntegrationHelper
|
8
|
+
PORT = 2600
|
9
|
+
BASE_URL = "http://localhost:#{PORT}"
|
10
|
+
|
11
|
+
def app(&block)
|
12
|
+
Thread.current[:app] = Thread.new do
|
13
|
+
Class.new(Rosendo::App, &block).run!(port: PORT, out: File.open(File::NULL, "w"))
|
14
|
+
end
|
15
|
+
sleep (ENV['WAIT'] && ENV['WAIT'].to_f) || 0.001
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(path, headers = {})
|
19
|
+
uri = URI.parse(BASE_URL + path)
|
20
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
21
|
+
response = http.get(path, headers)
|
22
|
+
@last_response = Response.new(response)
|
23
|
+
end
|
24
|
+
|
25
|
+
def response
|
26
|
+
@last_response
|
27
|
+
end
|
28
|
+
|
29
|
+
class Response
|
30
|
+
def initialize(response)
|
31
|
+
@response = response
|
32
|
+
end
|
33
|
+
|
34
|
+
def body
|
35
|
+
@response.body
|
36
|
+
end
|
37
|
+
|
38
|
+
def status
|
39
|
+
@response.code.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def headers
|
43
|
+
@response.to_hash
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class UnitTest < MiniTest::Unit::TestCase
|
50
|
+
include TestHelper
|
51
|
+
end
|
52
|
+
|
53
|
+
class IntegrationTest < UnitTest
|
54
|
+
include IntegrationHelper
|
55
|
+
|
56
|
+
def teardown
|
57
|
+
return unless app = Thread.current[:app]
|
58
|
+
app.raise Rosendo::Server::Stop
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rosendo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Sergio Gil
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-03-24 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email: sgilperez@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.md
|
29
|
+
files:
|
30
|
+
- README.md
|
31
|
+
- LICENSE
|
32
|
+
- example.rb
|
33
|
+
- lib/rosendo/app.rb
|
34
|
+
- lib/rosendo/dsl.rb
|
35
|
+
- lib/rosendo/request.rb
|
36
|
+
- lib/rosendo/response.rb
|
37
|
+
- lib/rosendo/routes.rb
|
38
|
+
- lib/rosendo/server.rb
|
39
|
+
- lib/rosendo.rb
|
40
|
+
- test/integration/basic_test.rb
|
41
|
+
- test/test_helper.rb
|
42
|
+
- test/unit/sample_test.rb
|
43
|
+
homepage: http://github.com/porras/rosendo
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options:
|
48
|
+
- --main
|
49
|
+
- README.md
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.8.24
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Minimalistic and naive Sinatra reimplementation, without any dependencies other than the ruby socket library
|
77
|
+
test_files: []
|
78
|
+
|
79
|
+
has_rdoc:
|