jersey 0.0.3
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 +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
|