expressr 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -0,0 +1,725 @@
1
+ Expressr
2
+ =======
3
+ Express.js for Ruby
4
+
5
+ Overview
6
+ --------
7
+
8
+ Expressr brings the architecture of Express.js to Ruby. It's a minimal and flexible web application framework that couples the concepts of Express.js and Node.js with the beauty of Ruby.
9
+
10
+ Expressr runs on top of [Noder](https://github.com/tombenner/noder) (Node.js for Ruby).
11
+
12
+ Quick Start
13
+ -----------
14
+
15
+ A web app can be created and started using the following script:
16
+
17
+ ```ruby
18
+ require 'expressr'
19
+
20
+ app = Expressr::App.new
21
+
22
+ app.get('/hello.txt') do |request, response|
23
+ response.out('Hello World')
24
+ end
25
+
26
+ app.listen(3000)
27
+ ```
28
+
29
+ To start the app, put the code into a file named `my_app.rb` and run it:
30
+
31
+ ```bash
32
+ $ ruby my_app.rb
33
+ Running Noder at 0.0.0.0:3000...
34
+ ```
35
+
36
+ Examples
37
+ --------
38
+
39
+ Here are some other examples of common usage:
40
+
41
+ ```ruby
42
+ require 'expressr'
43
+
44
+ app = Expressr::App.new
45
+
46
+ # Log every request to URLs beginning with /admin/
47
+ app.all('/admin/*') do |request, response|
48
+ Noder.logger.info "#{request.locals.user} accessed #{request.url}"
49
+ end
50
+
51
+ # Render JSON
52
+ app.get('/some_json') do |request, response|
53
+ response.out({ some: 'json' })
54
+ end
55
+
56
+ # Render a view
57
+ app.get('/users/:id') do (request, response)
58
+ Noder.with ->{ User.find(request.params.id) } do |user|
59
+ response.render('users/show', user: user.attributes)
60
+ end
61
+ end
62
+
63
+ # Respond to a POST request by creating a record and then redirecting
64
+ app.post('/comment') do |request, response|
65
+ Noder.with ->{ Comment.create(request.params.comment) } do |comment|
66
+ response.redirect("/comment/#{comment.id}")
67
+ endd
68
+ end
69
+
70
+ app.listen(3000)
71
+ ```
72
+
73
+ See the API section below for complete documentation.
74
+
75
+ Please note that the datastore-related lines in the examples above will block the event loop as written. You'll want to use [EM-Synchrony](https://github.com/igrigorik/em-synchrony)'s support for whichever datastore you're using and for other IO operations.
76
+
77
+ API
78
+ ---
79
+
80
+ ### Expressr::App
81
+
82
+ `Expressr::App` lets you create and run web apps.
83
+
84
+ #### Settings
85
+
86
+ An app has settings which configure it:
87
+
88
+ * `'jsonp callback name'` - The param used for determining the JSONP callback's name. The default is `'callback'` (e.g. for `?callback=myFunction`).
89
+ * `'locals'` - A hash of name-value pairs that will be passed to views as local variables.
90
+ * `'root'` - The root directory of the app. This is set automatically.
91
+ * `'view engine'` - The template engine used for views. The default is `'slim'`, and `'haml'` is also supported.
92
+ * `'views'` - The path to the views within the app's root directory. The default value is `'views'`.
93
+
94
+ Settings can be set using `#set` and retrieved using `#get`:
95
+
96
+ ```ruby
97
+ app.set('view engine', 'haml')
98
+ app.get('view engine') # "haml"
99
+ ```
100
+
101
+ A hash of all settings can be accessed by using `app.settings`.
102
+
103
+ #### .new(server_options={})
104
+
105
+ Creates the app.
106
+
107
+ ##### server_options
108
+
109
+ Please see [Noder's docs](https://github.com/tombenner/noder) for the options that can be passed to `Noder::HTTP::Server`. These include options like the server's address, port, whether HTTPS is enabled, etc.
110
+
111
+ #### #set(name, value)
112
+
113
+ Sets the value of a setting.
114
+
115
+ #### #get(name)
116
+
117
+ Gets the value of a setting.
118
+
119
+ #### #enable(name)
120
+
121
+ Sets the value of a setting to `true`.
122
+
123
+ #### #disable(name)
124
+
125
+ Sets the value of a setting to `false`.
126
+
127
+ #### #enabled?(name)
128
+
129
+ Returns a boolean of whether the setting is enabled or not.
130
+
131
+ #### #disabled?(name)
132
+
133
+ Returns a boolean of whether the setting is disabled or not.
134
+
135
+ #### #engine(value)
136
+
137
+ Sets the view engine. This is the equivalent of `set('view engine', value)`. Valid values are `'slim'` and `'haml'`.
138
+
139
+ #### #param(name, &block)
140
+
141
+ Registers a listener for any request that includes the specified param. For example, in a request is made to `/user/5` (with a `/user/:user_id` route) or `/profile?user_id=5`, the following will the log the `user_id` value:
142
+
143
+ ```ruby
144
+ app.param('user_id') do |request, response, continue, user_id|
145
+ user = User.find(user_id)
146
+ if user
147
+ request.locals.user = user
148
+ else
149
+ Noder.logger.info "User not found: #{user_id}"
150
+ end
151
+ end
152
+ ```
153
+
154
+ #### #VERB(path, &block)
155
+
156
+ Registers a listener for any request that matches the VERB (e.g. `get`, `post`, `put`, `delete`) and the path.
157
+
158
+ Respond with `Welcome!` for GET requests to `/welcome`:
159
+
160
+ ```ruby
161
+ app.get('/welcome') do |request, response|
162
+ response.out('Welcome!')
163
+ end
164
+ ```
165
+
166
+ Respond with JSON for POST requests to `/user/5/settings`:
167
+
168
+ ```ruby
169
+ app.post('/user/:id/settings') do |request, response|
170
+ response.out({
171
+ user_id: request.params.id,
172
+ params: request.params
173
+ })
174
+ end
175
+ ```
176
+
177
+ Regular expressions can also be used:
178
+
179
+ ```ruby
180
+ app.get(/^\/commits\/(\w+)\.\.(\w+)/) do |request, response|
181
+ response.out({
182
+ from: request.params[0],
183
+ to: request.params[1]
184
+ })
185
+ end
186
+ ```
187
+
188
+ #### #all(path, &block)
189
+
190
+ This method functions just like the `#VERB(path, &block)` method, but it matches all HTTP verbs.
191
+
192
+ It's very useful for creating global logic for all requests or for requests to specific paths:
193
+
194
+ ```ruby
195
+ app.all('/admin/*') do |request, response|
196
+ if !is_admin?
197
+ response.status = 403
198
+ response.out('Not authorized.')
199
+ end
200
+ end
201
+ ```
202
+
203
+ #### #route(path)
204
+
205
+ Returns an instance of a single route which can then be used to handle HTTP verbs with optional middleware. Using `#route(path)` is a recommended approach to avoiding duplicate route naming and thus typo errors.
206
+
207
+ ```ruby
208
+ app.route('/users').
209
+ all do |request, response|
210
+ Noder.logger.info "Users request performed"
211
+ end.
212
+ get do |request, response|
213
+ response.out(User.find(request.params.user_id))
214
+ end.
215
+ post do |request, response|
216
+ user = User.create(request.params.user)
217
+ response.out(user.attributes)
218
+ end
219
+ ```
220
+
221
+ #### #locals
222
+
223
+ Application local variables are provided to all templates rendered within the application. This is useful for providing helper functions to templates, as well as app-level data.
224
+
225
+ ```ruby
226
+ app.locals.site_name = 'My Site'
227
+ app.locals.contact_email = 'contact@mysite.com'
228
+ ```
229
+
230
+ #### #listen(port=nil, address=nil, &block)
231
+
232
+ Bind and listen for connections on the given host and port. This method is identical to Noder's `Noder::HTTP::Server#listen`.
233
+
234
+ ```ruby
235
+ app = Expressr::App.new
236
+ app.get('/hello.txt') do |request, response|
237
+ response.out('Hello World')
238
+ end
239
+ app.listen(3000)
240
+ ```
241
+
242
+ A block which will be called for all requests can be passed to it, too:
243
+
244
+ ```ruby
245
+ app = Expressr::App.new
246
+ app.listen do |request, response|
247
+ response.out('Hello World')
248
+ end
249
+ ```
250
+
251
+ #### #close
252
+
253
+ Stops the app. This is the same as Noder's `Noder::HTTP::Server#close` and is called when an `INT` or `TERM` signal is sent to a running server's process (e.g. when `Control-C` is pressed).
254
+
255
+ #### #settings
256
+
257
+ A hash of the app's settings:
258
+
259
+ ```ruby
260
+ app.set('my setting', 'My value')
261
+ value = app.settings['my setting']
262
+ ```
263
+
264
+ ### Expressr::Request
265
+
266
+ `Expressr::Request` inherits from (and thus also includes methods from) `Noder::HTTP::Request`.
267
+
268
+ #### #params
269
+
270
+ Similar to Rails' `params`, this includes params from the query string, POST data, and route parameters. Params can be accessed in three ways: `params.user_id`, `params[:user_id]`, or `params['user_id']`.
271
+
272
+ For example, a request to `/user/3?comment_id=4` will include two params:
273
+
274
+ ```ruby
275
+ app.get('/user/:user_id') do |request, response|
276
+ response.out({
277
+ user_id: request.params.user_id,
278
+ comment_id: request.params.comment_id
279
+ })
280
+ end
281
+ ```
282
+
283
+ If a regex route is used, the matches can be accessed at their integer indexes:
284
+
285
+ ```ruby
286
+ app.get(/\/user\/(\d+)\/comment\/(\d+)/) do |request, response|
287
+ response.out({
288
+ user_id: request.params[0],
289
+ comment_id: request.params[1]
290
+ })
291
+ end
292
+ ```
293
+
294
+ #### #query
295
+
296
+ Similar to `#params`, but it only includes params from the query string.
297
+
298
+ For example, a request to `/user/3?comment_id=4` will only include `comment_id`:
299
+
300
+ ```ruby
301
+ app.get('/user/:user_id') do |request, response|
302
+ response.out({
303
+ comment_id: request.query.comment_id
304
+ })
305
+ end
306
+ ```
307
+
308
+ #### #param(name)
309
+
310
+ Returns the value of param `name` when present. This is the equivalent of `params.name` or `params[name]`, which are the preferred forms.
311
+
312
+ ```ruby
313
+ request.param('user_id')
314
+ ```
315
+
316
+ #### #get(name)
317
+
318
+ Returns the value of the `name` header when present.
319
+
320
+ ```ruby
321
+ request.get('Content-Type') # "text/plain"
322
+ request.get('Something') # nil
323
+ ```
324
+
325
+ Aliased as `#header(name)`.
326
+
327
+ #### #accepts(types)
328
+
329
+ Check if the given types are acceptable, returning the best match when true, otherwise `nil`.
330
+
331
+ ```ruby
332
+ # Accept: text/*, application/json
333
+ request.accepts('text/html') # "text/html"
334
+ request.accepts('image/png') # nil
335
+ ```
336
+
337
+ #### #is?(type)
338
+
339
+ Check if the given types are acceptable, returning the best match when true, otherwise `nil`.
340
+
341
+ ```ruby
342
+ # Content-Type: text/html; charset=utf-8
343
+ request.is('text/html') # true
344
+ request.is('image/png') # false
345
+ ```
346
+
347
+ #### #ip
348
+
349
+ Returns the remote IP address.
350
+
351
+ ```ruby
352
+ request.ip # "68.1.8.45"
353
+ ```
354
+
355
+ #### #path
356
+
357
+ Returns the path of the requested URL.
358
+
359
+ ```ruby
360
+ # example.com/users?sort=desc
361
+ request.path # "/users"
362
+ ```
363
+
364
+ #### #host
365
+
366
+ Returns the hostname from the "Host" header field (without the port).
367
+
368
+ ```ruby
369
+ # Host: "example.com:3000"
370
+ request.host # "example.com"
371
+ ```
372
+
373
+ #### #xhr?
374
+
375
+ Check whether the request was issued with the "X-Requested-With" header field set to "XMLHttpRequest" (jQuery etc).
376
+
377
+ ```ruby
378
+ # Host: "example.com:3000"
379
+ request.xhr? # false
380
+ ```
381
+
382
+ #### #protocol
383
+
384
+ Returns the protocol string of the request (e.g. `'http'`, `'https'`).
385
+
386
+ ```ruby
387
+ # "http://example.com/"
388
+ request.protocol # 'http'
389
+ ```
390
+
391
+ #### #secure?
392
+
393
+ Checks whether a TLS connection is established. This is the equivalent of `request.protocol == 'https'`.
394
+
395
+ ```ruby
396
+ # "http://example.com/"
397
+ request.secure? # false
398
+ ```
399
+
400
+ #### #subdomains
401
+
402
+ Returns the subdomains as an array
403
+
404
+ ```ruby
405
+ # Host: "tobi.ferrets.example.com"
406
+ request.subdomains # ["ferrets", "tobi"]
407
+ ```
408
+
409
+ #### #original_url
410
+
411
+ This is similar to `#url`, except that it retains the original URL, allowing you to rewrite `#url` freely.
412
+
413
+ ```ruby
414
+ # /search?q=something
415
+ request.original_url # "/search?q=something"
416
+ ```
417
+
418
+ ### Expressr::Response
419
+
420
+ `Expressr::Response` inherits from (and thus also includes methods from) `Noder::HTTP::Response`.
421
+
422
+ #### #set(name, value=nil)
423
+
424
+ Set a header's value, or pass a hash as a single argument to set multiple headers at once.
425
+
426
+ ```ruby
427
+ response.set('Content-Type', 'text/plain')
428
+ response.set({
429
+ 'Content-Type' => 'text/plain',
430
+ 'Content-Length' => '123',
431
+ 'ETag' => '12345'
432
+ })
433
+ ```
434
+
435
+ #### #get(name)
436
+
437
+ Returns a header's value.
438
+
439
+ ```ruby
440
+ response.get('Content-Type') # "text/plain"
441
+ ```
442
+
443
+ #### #cookie(name, value, options={})
444
+
445
+ Sets a cookie. All of the options supported by [CGI::Cookie](http://ruby-doc.org/stdlib-1.9.3/libdoc/cgi/rdoc/CGI/Cookie.html) are supported.
446
+
447
+ ```ruby
448
+ response.cookie('user_id', '15')
449
+ response.cookie('remember_me', '1', {
450
+ 'expires' => Time.now + 14.days,
451
+ 'domain' => 'example.com'
452
+ })
453
+ ```
454
+
455
+ #### #clear_cookie(name, value, options={})
456
+
457
+ Sets a cookie. All of the options supported by [CGI::Cookie](http://ruby-doc.org/stdlib-1.9.3/libdoc/cgi/rdoc/CGI/Cookie.html) are supported.
458
+
459
+ ```ruby
460
+ response.cookie('user_id', '15')
461
+ response.clear_cookie('user_id')
462
+ ```
463
+
464
+ #### #redirect(status_or_url, url=nil)
465
+
466
+ Redirects to the specified URL with an optional status (default is `302`).
467
+
468
+ ```ruby
469
+ response.redirect('/foo/bar')
470
+ response.redirect(303, '/foo/bar')
471
+ response.redirect('https://www.google.com')
472
+ ```
473
+
474
+ #### #location(url)
475
+
476
+ Sets the `Location` header's value.
477
+
478
+ ```ruby
479
+ response.location('/foo/bar')
480
+ response.location('https://www.google.com')
481
+ ```
482
+
483
+ #### #out(status_or_content=nil, content=nil)
484
+
485
+ Sends the response. (This is the equivalent of Express.js's `send` method, which has another use in Ruby.)
486
+
487
+ ```ruby
488
+ response.out({ some: 'json' })
489
+ response.out('some html')
490
+ response.out(404, 'Sorry, we cannot find that!')
491
+ response.out(500, { error: 'something blew up' })
492
+ response.out(200)
493
+ ```
494
+
495
+ When the content is a string, the Content-Type is set to `text/html`.
496
+
497
+ When the content is a hash, the hash is converted to JSON and the Content-Type is set to `application/json`.
498
+
499
+ #### #json(status_or_content=nil, content=nil)
500
+
501
+ Sends a JSON response. This identical to `#out` when an array or object is passed.
502
+
503
+ ```ruby
504
+ response.json({ some: 'json' })
505
+ response.json(500, { error: 'something blew up' })
506
+ ```
507
+
508
+ #### #jsonp(status_or_content=nil, content=nil)
509
+
510
+ Sends a JSONP response. This identical to `#json`, but it provides JSONP support if the request specifies a JSONP callback.
511
+
512
+ ```ruby
513
+ # /?callback=foo
514
+ response.jsonp({ some: 'json' }) # "foo({"some":"json"});"
515
+ # /
516
+ response.jsonp({ some: 'json' }) # "{"some":"json"}"
517
+ ```
518
+
519
+ The JSONP callback name defaults to `'callback'`, but it can be set using the app's settings:
520
+
521
+ ```ruby
522
+ app.settings.set('jsonp callback name', 'cb')
523
+ # /?cb=foo
524
+ response.jsonp({ some: 'json' }) # "foo({"some":"json"});"
525
+ ```
526
+
527
+ #### #type(value)
528
+
529
+ Sets the `Content-Type` header to the value.
530
+
531
+ ```ruby
532
+ response.type('html')
533
+ response.type('application/json')
534
+ ```
535
+
536
+ #### #format(hash)
537
+
538
+ Performs content-negotiation on the request Accept header field when present.
539
+
540
+ ```ruby
541
+ response.format({
542
+ 'text/html' => proc { |request, response|
543
+ response.out("<h3>Some HTML</h3>")
544
+ },
545
+ 'text/plain' => proc { |request, response|
546
+ response.out("Some text")
547
+ },
548
+ 'application/json' => proc { |request, response|
549
+ response.out({ some: json })
550
+ },
551
+ })
552
+ ```
553
+
554
+ Content type synonyms are supported (e.g. `'json'` and `'application/json'` are equivalent):
555
+
556
+ ```ruby
557
+ response.format({
558
+ 'html' => proc { |request, response|
559
+ response.out("<h3>Some HTML</h3>")
560
+ },
561
+ 'text' => proc { |request, response|
562
+ response.out("Some text")
563
+ },
564
+ 'json' => proc { |request, response|
565
+ response.out({ some: json })
566
+ },
567
+ })
568
+ ```
569
+
570
+ #### #attachment(filename=nil)
571
+
572
+ Sets the Content-Disposition header field to "attachment". If a filename is given then the Content-Type will be automatically set based on the extension via `#type`, and the Content-Disposition's "filename=" parameter will be set.
573
+
574
+ ```ruby
575
+ response.attachment
576
+ # Content-Disposition: attachment
577
+
578
+ response.attachment('path/to/logo.png')
579
+ # Content-Disposition: attachment; filename="logo.png"
580
+ # Content-Type: image/png
581
+ ```
582
+
583
+ #### #send_file(path, options={})
584
+
585
+ Transfers the file at the given path.
586
+
587
+ Automatically defaults the Content-Type response header field based on the filename's extension.
588
+
589
+ ##### options
590
+
591
+ * `root` - Root directory for relative filenames
592
+
593
+ ```ruby
594
+ app.get('/user/:uid/photos/:file') do |request, response|
595
+ uid = request.params.uid
596
+ file = request.params.file
597
+
598
+ if request.locals.user.may_view_files_from(uid)
599
+ response.sendfile("/uploads/#{uid}/#{file}")
600
+ else
601
+ response.out(403, "Sorry! You can't see that.")
602
+ end
603
+ end
604
+ ```
605
+
606
+ #### #download(path, filename=nil)
607
+
608
+ Transfer the file at path as an "attachment". Typically browsers will prompt the user for download. The Content-Disposition "filename=" parameter (the one that will appear in the brower dialog is set to path by default), but you can also provide an override filename.
609
+
610
+ ```ruby
611
+ response.download('/report-12345.pdf')
612
+ response.download('/report-12345.pdf', 'report.pdf')
613
+ ```
614
+
615
+ #### #links(links)
616
+
617
+ Join the given links to populate the "Link" response header field.
618
+
619
+ ```ruby
620
+ response.links({
621
+ next: 'http://api.example.com/users?page=2',
622
+ last: 'http://api.example.com/users?page=5'
623
+ })
624
+
625
+ # Link: <http://api.example.com/users?page=2>; rel="next",
626
+ # <http://api.example.com/users?page=5>; rel="last"
627
+ ```
628
+
629
+ #### #locals
630
+
631
+ Response local variables are scoped to the request, thus only available to the view(s) rendered during that request / response cycle, if any.
632
+
633
+ This object is useful for exposing request-level information such as the request pathname, authenticated user, user settings, etc.
634
+
635
+ ```ruby
636
+ app.use do (request, response)
637
+ response.locals.user = User.find(request.params.user_id)
638
+ response.locals.authenticated = !response.locals.user.is_anonymous?
639
+ end
640
+ ```
641
+
642
+ #### #render(view, locals=nil, &block)
643
+
644
+ Renders a `view`. The view's local variables are supplied by both the `locals` argument and the app's `locals` setting. If the rendering raises an exception, the `&block` is called with the exception as an argument.
645
+
646
+ ```ruby
647
+ app.get('/users/:id') do (request, response)
648
+ user = User.find(request.params.id)
649
+ response.render('profile', user: user.attributes)
650
+ end
651
+
652
+ app.get('/contact') do (request, response)
653
+ response.render('contact') do |exception|
654
+ response.render('error')
655
+ end
656
+ end
657
+ ```
658
+
659
+ To set the locals that will be passed to all views, use:
660
+
661
+ ```ruby
662
+ app.locals.site_name = 'My Site'
663
+ app.locals.contact_email = 'contact@mysite.com'
664
+ ```
665
+
666
+ By default, Expressr looks for views in the `views` directory (e.g. `views/profile.slim`).
667
+
668
+ To set the directory of the views, use:
669
+
670
+ ```ruby
671
+ app.set('views', File.expand_path('../my_views', __FILE__))
672
+ ```
673
+
674
+ Expressr uses the Slim template engine by default, but it also supports Haml:
675
+
676
+ ```ruby
677
+ app.set('view engine', 'haml')
678
+ ```
679
+
680
+ If you'd like to add support for other template engines, doing so is fairly straightforward; just grep for `'slim'` in the codebase, and the steps needed to support a new engine should be clear.
681
+
682
+ ### Expressr::Router
683
+
684
+ `Expressr::App` lets you create routes for your app. An app's router can be accessed at `app.router`.
685
+
686
+ ```ruby
687
+ app = Expressr::App.new
688
+ app.router.get('/hello.txt') do |request, response|
689
+ response.out('Hello World')
690
+ end
691
+ ```
692
+
693
+ #### #use(path=nil, &block)
694
+
695
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
696
+
697
+ #### #param(name, &block)
698
+
699
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
700
+
701
+ #### #use(path=nil, &block)
702
+
703
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
704
+
705
+ #### #VERB(path, &block)
706
+
707
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
708
+
709
+ #### #use(path=nil, &block)
710
+
711
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
712
+
713
+ #### #use(path=nil, &block)
714
+
715
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
716
+
717
+ #### #use(path=nil, &block)
718
+
719
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
720
+
721
+
722
+ License
723
+ -------
724
+
725
+ Expressr is released under the MIT License. Please see the MIT-LICENSE file for details.