jersey 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +233 -0
- data/Rakefile +10 -0
- data/examples/readme.ru +15 -0
- data/jersey.gemspec +27 -0
- data/lib/jersey.rb +10 -0
- data/lib/jersey/api.rb +12 -0
- data/lib/jersey/base.rb +31 -0
- data/lib/jersey/extensions/error_handler.rb +30 -0
- data/lib/jersey/extensions/route_signature.rb +16 -0
- data/lib/jersey/helpers/log.rb +7 -0
- data/lib/jersey/http_errors.rb +62 -0
- data/lib/jersey/log.rb +25 -0
- data/lib/jersey/logging/base_logger.rb +58 -0
- data/lib/jersey/logging/json_logger.rb +8 -0
- data/lib/jersey/logging/logfmt_logger.rb +37 -0
- data/lib/jersey/logging/mixins.rb +31 -0
- data/lib/jersey/middleware/request_id.rb +46 -0
- data/lib/jersey/middleware/request_logger.rb +38 -0
- data/lib/jersey/setup.rb +40 -0
- data/lib/jersey/time.rb +29 -0
- data/lib/jersey/version.rb +3 -0
- data/test/errors_test.rb +48 -0
- data/test/helper.rb +39 -0
- data/test/log_test.rb +55 -0
- data/test/request_logger_test.rb +161 -0
- data/test/setup_test.rb +13 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c73c2c478b36fbd1a0574323dcbb7f023264517d
|
4
|
+
data.tar.gz: d7f9a1f7e5cd742a9de16d62c12b8b9cce788137
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 81638bfb73758bf758ae0245c163f88c2bf01c9214736a036c74f00e4a8f3a6309888588e07be9878783835f4ebad5bf02c348be1ec5dbb2adcdac72637d095e
|
7
|
+
data.tar.gz: 49fc8716d35f5e8b8d1cffb85d912e391c5325b22e8e72a751b9aedebaee14a94f82d9e27727b92773808d660790bcd4f0f9f95914845ac1d002adb499efced3
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 csquared
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
# Jersey
|
2
|
+
|
3
|
+
<img src="http://geology.com/state-map/maps/new-jersey-physical-map.gif" height="200px" />
|
4
|
+
|
5
|
+
Because [worse is better](http://en.wikipedia.org/wiki/Worse_is_better).
|
6
|
+
|
7
|
+
Jersey is a gem for people who want to write excellent APIs but have their
|
8
|
+
own opinions on how to structure their code and projects.
|
9
|
+
|
10
|
+
Jersey provides sensible defaults that are composed with sensible pieces, making
|
11
|
+
it easy to compose your own stack or use Jesery's compositions.
|
12
|
+
|
13
|
+
## Features
|
14
|
+
- env-conf for easy ENV based configuration
|
15
|
+
- request context aware request logging
|
16
|
+
- structured data loggers - json and logfmt
|
17
|
+
- unified exception handling
|
18
|
+
|
19
|
+
### `Jersey.setup`
|
20
|
+
|
21
|
+
Setup embodies a few opinions we have about apps:
|
22
|
+
- Having a notion of 'environment' where the configuration lives
|
23
|
+
- Using a dependency manager
|
24
|
+
- Always using UTC for the Time zone
|
25
|
+
- Always printing times in ISO8601 format
|
26
|
+
|
27
|
+
`Jersey.setup` allows any app that has Gemfiles and `.env` files to
|
28
|
+
"just work" in development *and* production with just:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
require 'jersey'
|
32
|
+
Jersey.setup
|
33
|
+
```
|
34
|
+
|
35
|
+
Which is usually included as the first line of code in a library consuming
|
36
|
+
Jersey. For tests, you will want to set `RACK_ENV` to `test` before
|
37
|
+
requiring your library that uses the above.
|
38
|
+
|
39
|
+
### `Jersey::API::Base`
|
40
|
+
|
41
|
+
Combines all of the Jersey middleware with a few standard middleware
|
42
|
+
from Rack and sinatra-contrib.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
class API < Jersey::API::Base
|
46
|
+
get '/hello' do
|
47
|
+
Jersey.log(at: "hello")
|
48
|
+
'hello'
|
49
|
+
end
|
50
|
+
|
51
|
+
get '/not_found' do
|
52
|
+
raise NotFound, "y u no here?"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
|
58
|
+
```
|
59
|
+
$ curl http://localhost:9292/hello
|
60
|
+
```
|
61
|
+
|
62
|
+
Server logs:
|
63
|
+
```
|
64
|
+
at=start method=GET path=/hello request_id=2c018557-d246-4b04-a7d7-fc74bae67ec0 now=2014-11-11T18:04:25+00:00
|
65
|
+
at=hello now=2014-11-11T18:04:25+00:00 request_id=2c018557-d246-4b04-a7d7-fc74bae67ec0
|
66
|
+
at=finish method=GET path=/hello status=200 size#bytes=5 route_signature=/hello elapsed=0.000 request_id=2c018557-d246-4b04-a7d7-fc74bae67ec0 now=2014-11-11T18:04:25+00:00
|
67
|
+
```
|
68
|
+
|
69
|
+
Unified, structured logging with all the info you will wish you had:
|
70
|
+
|
71
|
+
```
|
72
|
+
$ curl http://localhost:9292/not_found | jq '.'
|
73
|
+
```
|
74
|
+
|
75
|
+
Server logs:
|
76
|
+
```
|
77
|
+
at=start method=GET path=/not_found request_id=f3085630-05cf-4314-a7dd-5855e752594b now=2014-11-11T18:05:15+00:00
|
78
|
+
at=finish method=GET path=/not_found status=404 size#bytes=6212 route_signature=/not_found elapsed=0.001 request_id=f3085630-05cf-4314-a7dd-5855e752594b now=2014-11-11T18:05:15+00:00
|
79
|
+
```
|
80
|
+
|
81
|
+
Response:
|
82
|
+
```json
|
83
|
+
{
|
84
|
+
"error": {
|
85
|
+
"type": "NotFound",
|
86
|
+
"message": "y u no here?",
|
87
|
+
"backtrace": [
|
88
|
+
"/Users/csquared/projects/jersey/examples/readme.ru:11:in `block in <class:API>'",
|
89
|
+
"/Users/csquared/projects/jersey/.bundle/bundle/ruby/2.1.0/gems/sinatra-1.4.5/lib/sinatra/base.rb:1603:in `call'",
|
90
|
+
....
|
91
|
+
]
|
92
|
+
}
|
93
|
+
}
|
94
|
+
```
|
95
|
+
|
96
|
+
Unified, strucutred error handling. Notice how all we needed to do was raise `NotFound`
|
97
|
+
and we get a 404 response code (in the server logs) and our error message as part of the JSON payload.
|
98
|
+
|
99
|
+
#### `Jersey::HTTP::Errors`
|
100
|
+
|
101
|
+
Includes Ruby `Error` objects named with camel case for all of the HTTP 4xx and 5xx
|
102
|
+
errors. This allows you to raise `NotFound` as an error that has the `STATUS_CODE`
|
103
|
+
defined.
|
104
|
+
|
105
|
+
Allows uniform HTTP error handling when combined with the `ErrorHandler` sinatra extension.
|
106
|
+
|
107
|
+
#### Usage
|
108
|
+
Mix-in to any class that wants to raise HTTP errors, usually an API class.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
class API < Sinatra::Base
|
112
|
+
include Jersey::HTTP::Errors
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
### `Jersey::Extensions::ErrorHandler`
|
117
|
+
|
118
|
+
Unifies error responses. If the error object's class has a `STATUS_CODE` defined (such as the
|
119
|
+
errors in `Jersey::HTTP::Errors`), this will use that as the HTTP return status. The error
|
120
|
+
message and backtraces are included in responses assuming that this in for an internal API
|
121
|
+
over secured channels and therefore favors ease of debugging over the security risk of
|
122
|
+
including the backtrace. This is something I may want to configure.
|
123
|
+
|
124
|
+
#### Usage
|
125
|
+
Register as a Sinatra extension
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class API < Sinatra::Base
|
129
|
+
register Jersey::Extensions::ErrorHandler
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
#### `Jersey::Extensions::RouteSignature`
|
134
|
+
Adds a `ROUTE_SIGNATURE` to the `env` for each request, which is the name of an api endpoint
|
135
|
+
as it is *defined* versus the path that reaches your app.
|
136
|
+
For example, when you define a route such as ` get "/hello/:id"`, the `ROUTE_SIGNATURE` would
|
137
|
+
equal `"/hello/:id"`.
|
138
|
+
When combined with the `RequestLogger`,
|
139
|
+
it greatly simplifies creating aggregate statistics about the traffic hitting various api endpoints.
|
140
|
+
|
141
|
+
*Note:* this is considered a hack and something that sinatra should, but does not, handle.
|
142
|
+
|
143
|
+
#### Usage
|
144
|
+
Register as a Sinatra extension
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
class API < Sinatra::Base
|
148
|
+
register Jersey::Extensions::RouteSignature
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
#### `Jersey::Middleware::RequestID`
|
153
|
+
|
154
|
+
Creates a random request id for every http request, stored both in thread local storage
|
155
|
+
via the `RequestStore` and in the Rack `env`.
|
156
|
+
|
157
|
+
|
158
|
+
Works with or without explicitly including `RequestStore::Middleware`.
|
159
|
+
|
160
|
+
#### Usage
|
161
|
+
Use as a Rack middleware
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
class API < Sinatra::Base
|
165
|
+
use Jersey::Middleware::RequestID
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
#### `Jersey::Middleware::RequestLogger`
|
170
|
+
|
171
|
+
Logs http start and finish and errors in a structured logging format.
|
172
|
+
|
173
|
+
It defaults to using the `Jersey.logger` singleton which is `RequestStore`-aware.
|
174
|
+
Anything in `RequestStore[:log]` will get appended to the log data. (This is how request ids
|
175
|
+
are made available to logger calls outside of HTTP blocks but within HTTP request lifecycles).
|
176
|
+
|
177
|
+
Because I think request_ids are important, the logger will try to get them from either the
|
178
|
+
`RequestStore` or the `env`.
|
179
|
+
|
180
|
+
Logs at request start:
|
181
|
+
|
182
|
+
at: "start",
|
183
|
+
request_id: env['REQUEST_ID'],
|
184
|
+
method: request.request_method,
|
185
|
+
path: request.path_info,
|
186
|
+
content_type: request.content_type,
|
187
|
+
content_length: request.content_length
|
188
|
+
|
189
|
+
Logs at request finish:
|
190
|
+
|
191
|
+
at: "finish",
|
192
|
+
method: request.request_method,
|
193
|
+
path: request.path_info,
|
194
|
+
status: status,
|
195
|
+
content_length: headers['Content-Length'],
|
196
|
+
route_signature: env['ROUTE_SIGNATURE'],
|
197
|
+
elapsed: (Time.now - @request_start).to_f,
|
198
|
+
request_id: env['REQUEST_ID']
|
199
|
+
|
200
|
+
|
201
|
+
#### Usage
|
202
|
+
Use as a Rack middleware
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
class API < Sinatra::Base
|
206
|
+
use Jersey::Middleware::RequestLogger
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
## Installation
|
211
|
+
|
212
|
+
Add this line to your application's Gemfile:
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
gem 'jersey'
|
216
|
+
```
|
217
|
+
|
218
|
+
And then execute:
|
219
|
+
|
220
|
+
$ bundle
|
221
|
+
|
222
|
+
Or install it yourself as:
|
223
|
+
|
224
|
+
$ gem install jersey
|
225
|
+
|
226
|
+
|
227
|
+
## Contributing
|
228
|
+
|
229
|
+
1. Fork it ( https://github.com/[my-github-username]/jersey/fork )
|
230
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
231
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
232
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
233
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/examples/readme.ru
ADDED
data/jersey.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'jersey/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "jersey"
|
8
|
+
spec.version = Jersey::VERSION
|
9
|
+
spec.authors = ["csquared"]
|
10
|
+
spec.email = ["christopher.continanza@gmail.com"]
|
11
|
+
spec.summary = %q{Write APIs in the New Jersey Style}
|
12
|
+
spec.description = %q{Set of composable middleware and helpers for production sinatra APIs}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "sinatra", "~> 1.4"
|
22
|
+
spec.add_dependency 'sinatra-contrib', "~> 1.4"
|
23
|
+
spec.add_dependency 'env-conf'
|
24
|
+
spec.add_dependency 'request_store'
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
end
|
data/lib/jersey.rb
ADDED
data/lib/jersey/api.rb
ADDED
data/lib/jersey/base.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "sinatra/base"
|
2
|
+
require 'sinatra/json'
|
3
|
+
require 'json'
|
4
|
+
require 'request_store'
|
5
|
+
|
6
|
+
# jersey
|
7
|
+
require 'jersey/http_errors'
|
8
|
+
require 'jersey/middleware/request_id'
|
9
|
+
require 'jersey/middleware/request_logger'
|
10
|
+
require 'jersey/extensions/route_signature'
|
11
|
+
require 'jersey/extensions/error_handler'
|
12
|
+
require 'jersey/helpers/log'
|
13
|
+
|
14
|
+
module Jersey::API
|
15
|
+
class Base < Sinatra::Base
|
16
|
+
include Jersey::HTTP::Errors
|
17
|
+
|
18
|
+
register Jersey::Extensions::RouteSignature
|
19
|
+
register Jersey::Extensions::ErrorHandler
|
20
|
+
|
21
|
+
use Rack::Deflater
|
22
|
+
use Rack::ConditionalGet
|
23
|
+
use Rack::ETag
|
24
|
+
|
25
|
+
use Jersey::Middleware::RequestID
|
26
|
+
use Jersey::Middleware::RequestLogger
|
27
|
+
|
28
|
+
helpers Sinatra::JSON
|
29
|
+
helpers Jersey::Helpers::Log
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Jersey::Extensions
|
2
|
+
module ErrorHandler
|
3
|
+
def self.registered(app)
|
4
|
+
app.set :dump_errors, false
|
5
|
+
app.set :raise_errors, false
|
6
|
+
app.set :show_exceptions, false
|
7
|
+
|
8
|
+
# APIS should always return meaningful standardized errors
|
9
|
+
# with statuses that best match HTTP conventions
|
10
|
+
#
|
11
|
+
# Places nicely with Jersey::HTTP::Errors
|
12
|
+
app.error do
|
13
|
+
content_type(:json)
|
14
|
+
e = env['sinatra.error']
|
15
|
+
# get status code from Jersey Errors
|
16
|
+
if e.class.const_defined?(:STATUS_CODE)
|
17
|
+
status(e.class::STATUS_CODE)
|
18
|
+
else
|
19
|
+
status(500)
|
20
|
+
end
|
21
|
+
|
22
|
+
json(error: {
|
23
|
+
type: e.class.name.split('::').last,
|
24
|
+
message: e.message,
|
25
|
+
backtrace: e.backtrace
|
26
|
+
})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Jersey::Extensions
|
2
|
+
module RouteSignature
|
3
|
+
def self.registered(app)
|
4
|
+
app.helpers do
|
5
|
+
def route_signature
|
6
|
+
env["ROUTE_SIGNATURE"]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def route(verb, path, *)
|
12
|
+
condition { env["ROUTE_SIGNATURE"] = path.to_s }
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|