Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# HTTP::Accept
Provides a robust set of parsers for dealing with HTTP `Accept`, `Accept-Language`, `Accept-Encoding`, `Accept-Charset` headers.
[](https://github.com/socketry/http-accept/actions?workflow=Test)
## Motivation
I've been [developing some tools for building RESTful endpoints](https://github.com/ioquatix/utopia/blob/master/lib/utopia/controller/respond.rb) and part of that involved versioning. After reviewing the options, I settled on using the `Accept: application/json;version=1` method [as outlined here](http://labs.qandidate.com/blog/2014/10/16/using-the-accept-header-to-version-your-api/).
The `version=1` part of the `media-type` is a `parameter` as defined by [RFC7231 Section 3.1.1.1](https://tools.ietf.org/html/rfc7231#section-3.1.1.1). After reviewing several existing different options for parsing the `Accept:` header, I noticed a disturbing trend: `header.split(',')`. Because parameters may contain quoted strings which contain commas, this is clearly not an appropriate way to parse the header.
I am concerned about correctness, security and performance. As such, I implemented this gem to provide a simple high level interface for both parsing and correctly interpreting these headers.
## Installation
Add this line to your application's Gemfile:
``` ruby
gem 'http-accept'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install http-accept
You can then require it in your code like so:
``` ruby
require 'http/accept'
```
## Usage
Here are some examples of how to parse various headers.
### Parsing Accept: headers
You can parse the incoming `Accept:` header:
``` ruby
media_types = HTTP::Accept::MediaTypes.parse("text/html;q=0.5, application/json; version=1")
expect(media_types[0].mime_type).to be == "application/json"
expect(media_types[0].parameters).to be == {'version' => '1'}
expect(media_types[1].mime_type).to be == "text/html"
expect(media_types[1].parameters).to be == {'q' => '0.5'}
```
Normally, you'd want to match the media types against some set of available mime types:
``` ruby
module ToJSON
def content_type
HTTP::Accept::ContentType.new("application", "json", charset: 'utf-8')
end
# Used for inserting into map.
def split(*args)
content_type.split(*args)
end
def convert(object, options)
object.to_json
end
end
module ToXML
# Are you kidding?
end
map = HTTP::Accept::MediaTypes::Map.new
map << ToJSON
map << ToXML
object, media_range = map.for(media_types)
content = object.convert(model, media_range.parameters)
response = [200, {'Content-Type' => object.content_type}, [content]]
```
### Parsing Accept-Language: headers
You can parse the incoming `Accept-Language:` header:
``` ruby
languages = HTTP::Accept::Languages.parse("da, en-gb;q=0.8, en;q=0.7")
expect(languages[0].locale).to be == "da"
expect(languages[1].locale).to be == "en-gb"
expect(languages[2].locale).to be == "en"
```
Normally, you'd want to match the languages against some set of available localizations:
``` ruby
available_localizations = HTTP::Accept::Languages::Locales.new(["en-nz", "en-us"])
# Given the languages that the user wants, and the localizations available, compute the set of desired localizations.
desired_localizations = available_localizations & languages
```
The `desired_localizations` in the example above is a subset of `available_localizations`.
`HTTP::Accept::Languages::Locales` provides an efficient data-structure for matching the Accept-Languages header to set of available localizations according to <https://tools.ietf.org/html/rfc7231#section-5.3.5> and <https://tools.ietf.org/html/rfc4647#section-2.3>
## Contributing
We welcome contributions to this project.
1. Fork it.
2. Create your feature branch (`git checkout -b my-new-feature`).
3. Commit your changes (`git commit -am 'Add some feature'`).
4. Push to the branch (`git push origin my-new-feature`).
5. Create new Pull Request.
### Developer Certificate of Origin
This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted.
### Contributor Covenant
This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.