utopia 1.7.1 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -3
  3. data/README.md +142 -11
  4. data/benchmarks/string_vs_symbol.rb +12 -0
  5. data/lib/utopia/command.rb +16 -13
  6. data/lib/utopia/content.rb +1 -5
  7. data/lib/utopia/content/node.rb +9 -4
  8. data/lib/utopia/{extensions/rack.rb → content/response.rb} +33 -30
  9. data/lib/utopia/content/tag.rb +14 -17
  10. data/lib/utopia/content/transaction.rb +19 -17
  11. data/lib/utopia/controller.rb +29 -8
  12. data/lib/utopia/controller/actions.rb +148 -0
  13. data/lib/utopia/controller/base.rb +9 -49
  14. data/lib/utopia/controller/respond.rb +1 -1
  15. data/lib/utopia/controller/rewrite.rb +9 -1
  16. data/lib/utopia/controller/variables.rb +1 -0
  17. data/lib/utopia/localization.rb +4 -1
  18. data/lib/utopia/middleware.rb +0 -2
  19. data/lib/utopia/path.rb +9 -0
  20. data/lib/utopia/path/matcher.rb +0 -1
  21. data/lib/utopia/redirection.rb +3 -2
  22. data/lib/utopia/session.rb +119 -2
  23. data/lib/utopia/session/lazy_hash.rb +1 -3
  24. data/lib/utopia/setup.rb +73 -0
  25. data/lib/utopia/static.rb +9 -2
  26. data/lib/utopia/version.rb +1 -1
  27. data/setup/examples/wiki/controller.rb +41 -0
  28. data/setup/examples/wiki/edit.xnode +15 -0
  29. data/setup/examples/wiki/index.xnode +10 -0
  30. data/setup/examples/wiki/welcome/content.md +3 -0
  31. data/setup/server/config/environment.yaml +1 -0
  32. data/setup/server/git/hooks/post-receive +4 -5
  33. data/setup/site/Gemfile +5 -0
  34. data/setup/site/config.ru +2 -1
  35. data/setup/site/config/environment.rb +5 -17
  36. data/setup/site/pages/_page.xnode +4 -2
  37. data/setup/site/pages/links.yaml +1 -1
  38. data/setup/site/pages/welcome/index.xnode +33 -15
  39. data/setup/site/public/_static/site.css +72 -4
  40. data/setup/site/tasks/utopia.rake +8 -0
  41. data/spec/utopia/{rack_spec.rb → content/response_spec.rb} +12 -19
  42. data/spec/utopia/content_spec.rb +2 -3
  43. data/spec/utopia/controller/{action_spec.rb → actions_spec.rb} +18 -32
  44. data/spec/utopia/controller/middleware_spec.rb +10 -10
  45. data/spec/utopia/controller/middleware_spec/controller/controller.rb +3 -3
  46. data/spec/utopia/controller/middleware_spec/controller/nested/controller.rb +1 -1
  47. data/spec/utopia/controller/middleware_spec/redirect/controller.rb +1 -1
  48. data/spec/utopia/controller/respond_spec.rb +3 -2
  49. data/spec/utopia/controller/respond_spec/api/controller.rb +2 -2
  50. data/spec/utopia/controller/respond_spec/errors/controller.rb +1 -1
  51. data/spec/utopia/controller/rewrite_spec.rb +1 -1
  52. data/spec/utopia/controller/sequence_spec.rb +12 -16
  53. data/spec/utopia/exceptions/handler_spec/controller.rb +2 -2
  54. data/spec/utopia/performance_spec/config.ru +1 -0
  55. data/spec/utopia/session_spec.rb +34 -1
  56. data/spec/utopia/session_spec.ru +3 -3
  57. data/spec/utopia/setup_spec.rb +2 -2
  58. data/utopia.gemspec +2 -2
  59. metadata +18 -12
  60. data/lib/utopia/controller/action.rb +0 -116
  61. data/lib/utopia/session/encrypted_cookie.rb +0 -118
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3390ed8d54ee8c008436db759edf55d1ed597125
4
- data.tar.gz: 400382f8bd1cf1cc415f1807b41b131a66b93cc1
3
+ metadata.gz: 0b9d8f6593ca274e5489f7d07153aa9756ac83d6
4
+ data.tar.gz: a26c9e0b6c439311684563a66af2b0c8cd36e0df
5
5
  SHA512:
6
- metadata.gz: 1c1c7ece8a229674da398b5e51603b9dbcde275519d7b279f442e35bc07ef40101c1b80ff75d8527f089fa7f970144c6257d177262372a9d024bcdc25902b406
7
- data.tar.gz: 8095d7678c15039a31c8f2a5b04850dc32bfb45c4f96141ce85869ee80bec1283a9609f14a71f9aba6830ca3a70ac7f2e1f8daafdce933cbcec67e4fcb4870c4
6
+ metadata.gz: 5369e9923cbd895aeda163bb87234a3ef596afe999283e44c8520197f2043921d2cfd1450bd6fe3237ba6d92867932182079e20cd7efdb921bd2a9bc651cd0d7
7
+ data.tar.gz: 01b933e7c4c1d83e400ee809d474b4962739737d88908eaf67c388a7d1dc05ba6f062a5d3f40fc4076968ea30b9fee419539e63f90cc422fee79dba55aa44db8
@@ -5,13 +5,12 @@ before_install:
5
5
  - git config --global user.email "samuel@oriontransfer.net"
6
6
  - git config --global user.name "Samuel Williams"
7
7
  rvm:
8
- - 2.1.8
9
8
  - 2.2.4
10
9
  - 2.3.0
11
10
  - ruby-head
12
- - rbx-2
11
+ - rbx
13
12
  env: COVERAGE=true BENCHMARK=true
14
13
  matrix:
15
14
  allow_failures:
16
- - rvm: rbx-2
15
+ - rvm: rbx
17
16
  - rvm: ruby-head
data/README.md CHANGED
@@ -1,14 +1,21 @@
1
1
  # ![Utopia](materials/utopia.png?raw=true)
2
2
 
3
- Utopia is a website generation framework which provides a robust set of tools
4
- to build highly complex dynamic websites. It uses the filesystem heavily for
5
- content and provides functions for interacting with files and directories as
6
- structure representing the website.
3
+ Utopia is a website generation framework which provides a robust set of tools to build highly complex dynamic websites. It uses the filesystem heavily for content and provides functions for interacting with files and directories as structure representing the website.
7
4
 
8
5
  [![Build Status](https://secure.travis-ci.org/ioquatix/utopia.svg)](http://travis-ci.org/ioquatix/utopia)
9
6
  [![Code Climate](https://codeclimate.com/github/ioquatix/utopia.svg)](https://codeclimate.com/github/ioquatix/utopia)
10
7
  [![Coverage Status](https://coveralls.io/repos/ioquatix/utopia/badge.svg)](https://coveralls.io/r/ioquatix/utopia)
11
8
 
9
+ ## Motivation
10
+
11
+ The [original Utopia project](https://github.com/ioquatix/utopia-php) was written in PHP in the early 2000s. It consisted of an XML parser, a database layer and some code to assist with business logic. It was initially designed to reduce the amount of HTML required to build both content-centric websites and business apps. At the time, CSS was very poorly supported and thus a lot of the time, you'd be using quite complex `<table>`s with embedded `<img>`s to generate simple things like boxes with drop shadows, etc. Utopia provided a core concept - a node - which was essentially a small snippet of HTML, which could be composed into other nodes simply by using a named tag (similar to ColdFusion). Attributes and content were passed in, and thus you could easily build complex pages with simple semantic markup.
12
+
13
+ At the time, the available frameworks were pretty basic. Utopia was a working, albeit poor, implementation of MVC and supported several commercial websites I developed at the time. I made it, partly just because I could, but also because it served a commercial purpose.
14
+
15
+ Eventually one day I started using Ruby on Rails. There are aspects of the Rails framework which I like. However, at the time I was using it (starting with version 0.8), I found that it's flat organisation of controllers and views very limiting. Nested controllers and views make it easier to manage complexity in a web application. Utopia embraces this principle, and applies it to both the controller and view layers. I also developed a [model layer with similar principles](https://github.com/ioquatix/relaxo-model).
16
+
17
+ So, Utopia exists because it suits my way of thinking about web applications, and it's conceptual core has been refined for over a decade. It provides a considered amount of both flexibility, and opinionated behavior.
18
+
12
19
  ## Installation
13
20
 
14
21
  ### Local Setup
@@ -19,9 +26,10 @@ Install utopia:
19
26
 
20
27
  Create a new site:
21
28
 
22
- $ utopia create www.example.com
29
+ $ mkdir www.example.com
23
30
  $ cd www.example.com
24
- $ rake server
31
+ $ utopia site create
32
+ $ rake
25
33
 
26
34
  #### Bower Integration
27
35
 
@@ -44,13 +52,17 @@ Firstly log into your remote site using `ssh` and install utopia:
44
52
 
45
53
  Then use the utopia command to generate a new remote site:
46
54
 
47
- $ sudo -u http utopia server:create /srv/http/www.example.com
55
+ $ mkdir /srv/http/www.example.com
56
+ $ cd /srv/http/www.example.com
57
+ $ sudo -u http utopia server create
48
58
 
49
59
  On the local site, you can set up a git remote:
50
60
 
51
61
  $ git remote add production ssh://remote/srv/http/www.example.com
52
62
  $ git push --set-upstream production master
53
63
 
64
+ When you push to the remote site, `rake deploy` will be run after the code is updated, finally, `rake restart` will be run which ideally should restart the application server.
65
+
54
66
  ### Passenger+Nginx Setup
55
67
 
56
68
  Utopia works well with Passenger+Nginx. Installing Passenger+Nginx is easy:
@@ -101,31 +113,150 @@ Utopia builds on top of Rack with the following middleware:
101
113
  - `Utopia::Localization`: Non-intrusive localization of resources.
102
114
  - `Utopia::Controller`: Dynamic behaviour with recursive execution.
103
115
  - `Utopia::Content`: XML-style template engine with powerful tag behaviours.
104
- - `Utopia::Session::EncryptedCookie`: Session storage using an encrypted cookie.
116
+ - `Utopia::Session`: Session storage using an encrypted cookie.
117
+
118
+ The implementation of Utopia is considered thread-safe and reentrant. However, this does not guarantee that the code YOU write will be so.
105
119
 
106
120
  ### Static
107
121
 
108
- This middleware serves static files using the `mime-types` library. By default, it works with `Rack::Sendfile` and `Rack::Cache` and supports `ETag` based caching.
122
+ This middleware serves static files using the `mime-types` library. By default, it works with `Rack::Sendfile` and supports `ETag` based caching. Normally, you'd prefer to put static files into `public/_static` but it's also acceptable to put static content into `pages/` if it makes sense.
123
+
124
+ use Utopia::Static,
125
+ # The root path to serve files from:
126
+ root: "path/to/root",
127
+ # The mime-types to recognize/serve:
128
+ types: [:default, :xiph],
129
+ # Cache-Control header for files:
130
+ cache_control: 'public, max-age=7200'
109
131
 
110
132
  ### Redirection
111
133
 
112
134
  A set of flexible URI rewriting middleware which includes support for string mappings, regular expressions and status codes (e.g. 404 errors).
113
135
 
136
+ # String (fast hash lookup) rewriting:
137
+ use Utopia::Redirection::Rewrite,
138
+ '/' => '/welcome/index'
139
+
140
+ # Redirect directories (e.g. /) to an index file (e.g. /index):
141
+ use Utopia::Redirection::DirectoryIndex,
142
+ index: 'index.html'
143
+
144
+ # Redirect (error) status codes to actual pages:
145
+ use Utopia::Redirection::Errors,
146
+ 404 => '/errors/file-not-found'
147
+
114
148
  ### Localization
115
149
 
116
150
  The localization middleware uses the `Accept-Language` header to guess the preferred locale out of the given options. If a request path maps to a resource, that resource is returned. Otherwise, a localized request is made.
117
151
 
152
+ use Utopia::Localization,
153
+ :default_locale => 'en',
154
+ :locales => ['en', 'de', 'ja', 'zh'],
155
+ :nonlocalized => ['/_static/', '/_cache/']
156
+
157
+ Somewhere further down the chain, you can localize a resource:
158
+
159
+ localization = Utopia::Localization[request]
160
+ show_welcome(localization.current_locale)
161
+
118
162
  ### Controller
119
163
 
120
- A simple recursive controller layer which works in isolation from the view rendering middleware. A controller consists of a set of actions which match against incoming paths and execute code accordingly.
164
+ A simple recursive controller layer which works in isolation from the view rendering middleware.
165
+
166
+ use Utopia::Controller,
167
+ # The root directory where `controller.rb` files can be found.
168
+ root: 'path/to/root',
169
+ # The base class to use for all controllers:
170
+ base: Utopia::Controller::Base
171
+ # Whether or not to cache controller classes:
172
+ cache_controllers: (RACK_ENV == :production),
173
+
174
+ A controller is a file within the root directory (or subdirectory) with the name `controller.rb`. This code is dynamically loaded into an anonymous class and executed. The default controller has only a single function:
175
+
176
+ def passthrough(request, path)
177
+ # Call one of:
178
+
179
+ # This will cause the middleware to generate a response.
180
+ # def respond!(response)
181
+
182
+ # This will cause the controller to skip the request.
183
+ # def ignore!
184
+
185
+ # Request relative redirect. Respond with a redirect to the given target.
186
+ # def redirect! (target, status = 302)
187
+
188
+ # Controller relative redirect.
189
+ # def goto!(target, status = 302)
190
+
191
+ # Respond with an error which indiciates some kind of failure.
192
+ # def fail!(error = 400, message = nil)
193
+
194
+ # Succeed the request and immediately respond.
195
+ # def succeed!(status: 200, headers: {}, **options)
196
+ # options may include content: string or body: Enumerable (as per Rack specifications
197
+ end
198
+
199
+ The controller layer can do more complex operations by prepending modules into it.
200
+
201
+ prepend Rewrite, Actions
202
+
203
+ # Extracts an Integer
204
+ rewrite.extract_prefix id: Integer do
205
+ @user = User.find_by_id(@id)
206
+ end
207
+
208
+ before do |request, path|
209
+ # Always executed, before any controller specific actions are performed.
210
+ end
211
+
212
+ on 'edit' do |request, path|
213
+ if request.post?
214
+ @user.update_attributes(request[:user])
215
+ end
216
+ end
217
+
218
+ otherwise do |request, path|
219
+ # Executed if no specific named actions were executed.
220
+ end
221
+
222
+ after do |request, path|
223
+ # Always executed, after all other controller actions are performed.
224
+ end
121
225
 
122
226
  ### Content
123
227
 
124
228
  A tag based content generation system which integrates nicely with HTML5. Supports structures which separate generic page templates from dynamically generated content in an easy and consistent way.
125
229
 
230
+ use Utopia::Content,
231
+ cache_templates: (RACK_ENV == :production),
232
+ tags: {
233
+ 'deferred' => Utopia::Tags::Deferred,
234
+ 'override' => Utopia::Tags::Override,
235
+ 'node' => Utopia::Tags::Node,
236
+ 'environment' => Utopia::Tags::Environment.for(RACK_ENV)
237
+ }
238
+
239
+ A basic template `create.xnode` looks something like:
240
+
241
+ <page>
242
+ <heading>Create User</heading>
243
+ <form action="#">
244
+ <input name="name" />
245
+ <input type="submit" />
246
+ </form>
247
+ </page>
248
+
249
+ This template would typically be designed with supporting `_page.xnode` and `_heading.xnode` in the same directory or, more typically, somewhere further up the directory hierarchy.
250
+
126
251
  ### Session
127
252
 
128
- The encrypted cookie session management uses symmetric private key encryption to store data on the client and avoid tampering.
253
+ The session management uses symmetric private key encryption to store data on the client and avoid tampering.
254
+
255
+ use Utopia::Session,
256
+ :expire_after => 3600,
257
+ :secret => '40 or more random characters for your secret key'
258
+
259
+ All session data is stored on the client, but it's encrypted with a salt and the secret key. It would be hard for the client to decrypt the data without the secret.
129
260
 
130
261
  ## Contributing
131
262
 
@@ -0,0 +1,12 @@
1
+ require 'benchmark/ips'
2
+
3
+ STRING_HASH = { "foo" => "bar" }
4
+ SYMBOL_HASH = { :foo => "bar" }
5
+
6
+ Benchmark.ips do |x|
7
+ x.report("string") { STRING_HASH["foo"] }
8
+ x.report("symbol") { SYMBOL_HASH[:foo] }
9
+ x.report("symbol-from-string") { SYMBOL_HASH["foo".to_sym] }
10
+
11
+ x.compare!
12
+ end
@@ -34,11 +34,10 @@ module Utopia
34
34
  module Site
35
35
  CONFIGURATION_FILES = ['config.ru', 'config/environment.rb', 'Gemfile', 'Rakefile', 'tasks/utopia.rake']
36
36
 
37
- DIRECTORIES = ["config", "lib", "pages", "public", "tasks", "tmp"]
38
- SYMLINKS = {"public/_static" => "../pages/_static"}
37
+ DIRECTORIES = ["config", "lib", "pages", "public", "tasks"]
39
38
 
40
39
  # Removed during upgrade process
41
- OLD_DIRECTORIES = ["access_log", "cache"]
40
+ OLD_DIRECTORIES = ["access_log", "cache", "tmp"]
42
41
 
43
42
  ROOT = File.join(BASE, 'site')
44
43
  end
@@ -56,7 +55,6 @@ module Utopia
56
55
  destination_root = parent.root
57
56
 
58
57
  FileUtils.mkdir_p File.join(destination_root, "public")
59
- FileUtils.mkdir_p File.join(destination_root, "tmp")
60
58
 
61
59
  Dir.chdir(destination_root) do
62
60
  # Shared allows multiple users to access the site with the same group:
@@ -65,6 +63,7 @@ module Utopia
65
63
  system("git", "config", "core.worktree", destination_root)
66
64
 
67
65
  system("cp", "-r", File.join(Setup::Server::ROOT, 'git', 'hooks'), File.join(destination_root, '.git'))
66
+ system("cp", "-r", File.join(Setup::Server::ROOT, 'config'), File.join(destination_root))
68
67
  end
69
68
 
70
69
  hostname = `hostname`.chomp
@@ -110,10 +109,6 @@ module Utopia
110
109
  end
111
110
  end
112
111
 
113
- Setup::Site::SYMLINKS.each do |path, target|
114
- FileUtils.ln_s(target, File.join(destination_root, path), force: true)
115
- end
116
-
117
112
  Setup::Site::CONFIGURATION_FILES.each do |configuration_file|
118
113
  destination_path = File.join(destination_root, configuration_file)
119
114
 
@@ -160,6 +155,16 @@ module Utopia
160
155
  class Update < Samovar::Command
161
156
  self.description = "Upgrade an existing site to use the latest configuration files from the template."
162
157
 
158
+ def move_static!
159
+ if File.lstat("public/_static").symlink?
160
+ FileUtils.rm_f "public/_static"
161
+ end
162
+
163
+ if File.directory?("pages/_static") and !File.exist?("public/_static")
164
+ system("git", "mv", "pages/_static", "public/_static")
165
+ end
166
+ end
167
+
163
168
  def invoke(parent)
164
169
  destination_root = parent.root
165
170
  branch_name = "utopia-upgrade-#{Utopia::VERSION}"
@@ -180,10 +185,6 @@ module Utopia
180
185
  FileUtils.rm_rf(path)
181
186
  end
182
187
 
183
- Setup::Site::SYMLINKS.each do |path, target|
184
- FileUtils.ln_s(target, File.join(destination_root, path), force: true)
185
- end
186
-
187
188
  Setup::Site::CONFIGURATION_FILES.each do |configuration_file|
188
189
  source_path = File.join(Setup::Site::ROOT, configuration_file)
189
190
  destination_path = File.join(destination_root, configuration_file)
@@ -201,7 +202,9 @@ module Utopia
201
202
  system("git", "add", "-u")
202
203
 
203
204
  # Stage any new files that we have explicitly added:
204
- system("git", "add", *Setup::Site::CONFIGURATION_FILES, *Setup::Site::SYMLINKS.keys)
205
+ system("git", "add", *Setup::Site::CONFIGURATION_FILES)
206
+
207
+ move_static!
205
208
 
206
209
  # Commit all changes:
207
210
  system("git", "commit", "-m", "Upgrade to utopia #{Utopia::VERSION}.")
@@ -145,13 +145,9 @@ module Utopia
145
145
  locale = env[Localization::CURRENT_LOCALE_KEY]
146
146
  if link = Links.for(@root, path, locale)
147
147
  if link.path and node = lookup_node(link.path)
148
- response = Rack::Response.new
149
-
150
148
  attributes = request.env.fetch(VARIABLES_KEY, {}).to_hash
151
149
 
152
- node.process!(request, response, attributes)
153
-
154
- return response.finish
150
+ return node.process!(request, attributes)
155
151
  elsif redirect_uri = link[:uri]
156
152
  return [307, {HTTP::LOCATION => redirect_uri}, []]
157
153
  end
@@ -107,18 +107,23 @@ module Utopia
107
107
  end
108
108
 
109
109
  def call(transaction, state)
110
+ # Load the template:
110
111
  template = @controller.fetch_template(@file_path)
111
112
 
113
+ # Evaluate the template/code:
112
114
  context = Context.new(transaction, state)
113
115
  markup = template.to_buffer(context)
114
116
 
117
+ # Render the resulting markup into the transaction:
115
118
  transaction.parse_markup(markup)
116
119
  end
117
120
 
118
- def process!(request, response, attributes = {})
119
- transaction = Transaction.new(request, response)
121
+ def process!(request, attributes = {})
122
+ transaction = Transaction.new(request)
123
+
120
124
  output = transaction.render_node(self, attributes)
121
- response.write(output)
125
+
126
+ return [200, transaction.headers, [output]]
122
127
  end
123
128
  end
124
129
 
@@ -149,7 +154,7 @@ module Utopia
149
154
  end
150
155
 
151
156
  def response
152
- transaction.response
157
+ transaction
153
158
  end
154
159
 
155
160
  def attributes
@@ -18,39 +18,42 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'rack'
22
-
23
- module Rack
24
- # Compatibility with older versions of rack:
25
- EXPIRES = 'Expires'.freeze unless defined? EXPIRES
26
- HTTP_HOST = 'HTTP_HOST'.freeze unless defined? HTTP_HOST
27
-
28
- class Response
29
- # Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
30
- def do_not_cache!
31
- headers[CACHE_CONTROL] = "no-cache, must-revalidate"
32
- headers[EXPIRES] = Time.now.httpdate
33
- end
21
+ module Utopia
22
+ class Content
23
+ # Compatibility with older versions of rack:
24
+ EXPIRES = 'Expires'.freeze
25
+ CACHE_CONTROL = 'Cache-Control'.freeze
26
+ CONTENT_TYPE = 'Content-Type'.freeze
27
+
28
+ class Response
29
+ def initialize
30
+ @status = 200
31
+ @headers = {}
32
+ end
33
+
34
+ attr :status
35
+ attr :headers
36
+
37
+ # Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
38
+ def do_not_cache!
39
+ @headers[CACHE_CONTROL] = "no-cache, must-revalidate"
40
+ @headers[EXPIRES] = Time.now.httpdate
41
+ end
34
42
 
35
- # Specify that the content should be cached.
36
- def cache!(duration = 3600, access = "public")
37
- unless headers[CACHE_CONTROL] =~ /no-cache/
38
- headers[CACHE_CONTROL] = "#{access}, max-age=#{duration}"
39
- headers[EXPIRES] = (Time.now + duration).httpdate
43
+ # Specify that the content should be cached.
44
+ def cache!(duration = 3600, access: "public")
45
+ unless @headers[CACHE_CONTROL] =~ /no-cache/
46
+ @headers[CACHE_CONTROL] = "#{access}, max-age=#{duration}"
47
+ @headers[EXPIRES] = (Time.now + duration).httpdate
48
+ end
40
49
  end
41
- end
42
50
 
43
- # Specify the content type of the response data.
44
- def content_type!(value)
45
- self.content_type = value
46
- end
47
-
48
- def content_type= value
49
- headers[CONTENT_TYPE] = value
50
- end
51
-
52
- def content_type
53
- headers[CONTENT_TYPE]
51
+ # Specify the content type of the response data.
52
+ def content_type= value
53
+ @headers[CONTENT_TYPE] = value
54
+ end
55
+
56
+ alias content_type! content_type=
54
57
  end
55
58
  end
56
59
  end