-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocs.html
More file actions
615 lines (453 loc) · 18.2 KB
/
docs.html
File metadata and controls
615 lines (453 loc) · 18.2 KB
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
---
layout: main
---
<h1>Documentation</h1>
<a class="anchor" name="getting_started"></a>
<section>
<h2>Getting Started</h2>
<p>
Getting started with SparkGS could not be easier. Follow these four steps:
</p>
<p>1. Install <a href="http://gosu-lang.github.io">Gosu</a></p>
<p>2. Save this code to <code>spark.gsp</code></p>
<pre class="prettyprint">
classpath "org.gosu-lang.gosu:sparkgs:0.1.0"
extends sparkgs.SparkGSFile
get('/', \-> "Hello World")
</pre>
<p>3. Fire it up: <code>$ gosu spark.gsp</code></p>
<p>4. Check it out at <a href="http://localhost:4567/">http://localhost:4567/</a>
</section>
<a class="anchor" name="routes"></a>
<section>
<h2>Defining Routes</h2>
<p>In SparkGS, as with SinatraRB and SparkJava, HTTP routes are defined with three components:</p>
<ul>
<li>An <a href="http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods">HTTP Verb</a></li>
<li>A Path</li>
<li>A Handler</li>
</ul>
<p>In SparkGS, the HTTP verb is a method call, the path is a string argument, and the handler is (usually) a block.</p>
<p>Here are a few example routes:</p>
<pre class="prettyprint">
// Routes a GET root of the webserver to a block, which simply returns "Hello World"
get('/', \-> "Hello World")
// Routes a POST to /inbox to the the InboxHandler class
post('/inbox', \-> InboxHandler.acceptInput(Request.Body) )
// Routes all verbs to /contacts to the the ContactsHandler class
handle('/contacts', \-> new ContactsHandler().handleRequest(Request, Response) )
// Routes a GET to a Gosu Template
get('/input_form', \-> view.InputForm.render(Writer) )
</pre>
<p>So, for each HTTP verb, there is a corresponding method, and each method accepts two arguments:</p>
<ul>
<li>A path defined by a string</li>
<li>A handler (typically a block)</li>
</ul>
<h3>Path Variables</h3>
<p>SparkJava (and, hence, SparkGS) support path variables and splats, which become available via
the <code>Params</code> property or <code>Splat</code> property during a request.</p>
<pre class="prettyprint">
get('/contact/:id', \-> "You asked for a contact with the ID ${Params['id']}")
get('/file/*', \-> "You asked for the file with path ${Request.Splat}")
</pre>
<h3>Request Properties</h3>
<p>In SparkGS programs, there are a few properties and methods available to for used in your handlers:</p>
<ul>
<li><code>Request</code> - Access to the HTTP Request info</li>
<li><code>Response</code> - Access to the HTTP Response</li>
<li><code>Writer</code> - Access to the Writer for the HTTP Response</li>
<li><code>Cookies</code> - Access to the Cookies for the HTTP Request and Response</li>
<li><code>Session</code> - Access to the HTTP Session</li>
<li><code>redirect(url)</code> - Redirect to the given URL</li>
</ul>
<p>These objects are documented more fully below, but most of them behave in the way you would expect.</p>
<h4>Accesing Request Info Elsewhere</h4>
<p>Note that <strong>any</strong> object can get access to these properties by simply implementing the
<code>IHasRequestContext</code> interface, which mixes these properties in via the
<code>RequestContextEnhancement</code> enhancement.</p>
<p>So you can move complicated logic out to </p>
</section>
<a class="anchor" name="requests"></a>
<section>
<h2>Requests</h2>
<p>The <code>Request</code> object in SparkGS is mostly a wrapper around the request object in
SparkJava, but it surfaces data via properties rather than as methods.
</p>
<p>So this SparkJava code:</p>
<pre class="prettyprint">
System.out.println(request.pathInfo());
</pre>
<p>Becomes:</p>
<pre class="prettyprint">
print(Request.PathInfo)
</pre>
<p>Here is a list of all the properties and methods of a request object:</p>
<pre class="prettyprint">
Request.Params // returns a parameter map
Request.Attributes // returns the attributes map
Request.SparkJavaRequest // returns the raw SparkJava request
Request.Session // returns the session
Request.IsGet // returns true if the current request is an HTTP GET
Request.IsPost // returns true if the current request is an HTTP POST
Request.IsPut // returns true if the current request is an HTTP PUT
Request.IsPatch // returns true if the current request is an HTTP PATCH
Request.IsDelete // returns true if the current request is an HTTP DELETE
Request.IsHead // returns true if the current request is an HTTP HEAD
Request.IsTrace // returns true if the current request is an HTTP TRACE
Request.IsConnect // returns true if the current request is an HTTP CONNECT
Request.IsOptions // returns true if the current request is an HTTP OPTIONS
Request.ContentType // returns the content type of the request
Request.Body // returns the body of the request
Request.ContentLength // returns the length of the body content
Request.ContextPath // returns the context path of the request
Request.Cookies // returns cookies of the request (see also the <a href="#cookies">CookieJar</a>, which abstracts cookies)
Request.Headers // returns a map of all request headers
Request.Host // returns the host of the request
Request.Ip // returns the IP of the request
Request.PathInfo // returns the path of the request
Request.UserAgent // returns the user agent of the request
Request.Splat // returns the splat of the request, or null if none exists
Request.Scheme // returns the scheme of the request (e.g. "http")
Request.URL // returns the full URL of the request
Request.Port // returns the port of the request
</pre>
</section>
<a class="anchor" name="responses"></a>
<section>
<h2>Responses</h2>
<p>Like <code>Requests</code>, SparkGS <code>Responses</code> are largely wrappers around the SparkJava response.</p>
<p>Here is an outline of the available functions:</p>
<pre class="prettyprint">
Response.Writer // returns the writer for this response
Response.SparkJavaResponse // returns the raw SparkJava response object
Response.Committed // true if the current response has been committed
Response.redirect(to, [optional] code) // redirects to the given url with an optional code (defaults to 302)
Response.Status = 401 // sets the response status to 401
Response.Type = "application/json" // sets the response type
</pre>
<h3>Handlers Explained</h3>
<p>Response handlers are a bit different in SparkGS than they are in SparkJava: they are typically (but not always
blocks) rather than anonymous implementations of an interface. As such, they have slightly different characteristics:</p>
<ul>
<li>
A handler can simply be a raw string:
<pre class="prettyprint">
get("/foo", "Foo!")
</pre>
</li>
<li>
A handler can be a block that returns a string:
<pre class="prettyprint">
get("/foo", \-> "Foo!")
</pre>
</li>
<li>
A handler can be a block that works with a writer and doesn't actually return anything:
<pre class="prettyprint">
get("/foo", \-> { Writer.append("Foo!") } )
</pre>
</li>
<li>
A handler can be a method reference to any method that does not take any argumetns:
<pre class="prettyprint">
get("/foo", MyController#foo() )
</pre>
In this case, a new instance of MyController will be created (if the method is non-static) and the
<code>foo()</code> method will be invoked.
</li>
</ul>
<p>These are all perfectly reasonable ways to use SparkGS.</p>
<p>Which one you use will depend on how complicated the logic is in your handler.</p>
</section>
<a class="anchor" name="templates"></a>
<section>
<h2>Templates</h2>
<p>One of the best extensions that SparkGS makes to SparkJava isn't even part of the SparkGS codebase: it's the
fact that Gosu comes with type-safe templating built into the language via Gosu Template Files!</p>
<p>This allows you to define a Gosu Template file (say at <code>/src/view/MyTemplate.gst</code>)</p>
<pre class="prettyprint">
<%@ params( name : String ) %>
<html>
<body>
<h1>Hello there ${name}!</h1>
</body>
</html>
</pre>
<p>And then invoke the template from your handler:</p>
<pre class="prettyprint">
get("/hello", \-> view.MyTemplate.render(Writer, Params['name']) )
</pre>
<p>Very clean!</p>
<h3>Layouts</h3>
<p>SparkGS supports a simple layout mechanism:</p>
<p>In your spark file, set the <code>Layout</code> property to be anything that satisfies the <code>sparkgs.Layout</code>
structure. This is typically via a Gosu Template File:</p>
<pre class="prettyprint">
Layout = view.MyLayout
//... route definitions
</pre>
<p>The layout should output the <code>body</code> string where it wants to include the body of the response:</p>
<pre class="prettyprint">
<%@ params( body : String ) %>
<html>
<body>
${body}
</body>
</html>
</pre>
<p>Note that you can override the layout at the beginning of an action as well.</p>
<h3>Form Helpers</h3>
<p>SparkGS has tools for generating some common HTML elements in Gosu templates.</p>
<p>For example, this boilerplate code to list off all possible values of an enum:</p>
<pre class="prettyprint">
<form action="/root">
<label for='Country[Continent]'>Continent</label>
<select name='Country[Continent]'>
<option value='Africa'>Africa</option>
//.. 5 more continents
<option value='Australia'>Asia</option>
</select>
<input type='submit' value='Submit'/>
</form>
</pre>
<p>Becomes:</p>
<pre class="prettyprint">
<form action="/root">
${SparkGSTemplate.selectInput(Country#Continent)}
${SparkGSTemplate.submitInput()}
</form>
</pre>
<p>Using Gosu's feature literals you are able to keep your views clean and intuitive.</p>
</section>
<a class="anchor" name="cookies"></a>
<section>
<h2>Cookies</h2>
<p>SparkGS abstracts cookies much the same way that <a href="http://rubyonrails.org/">Rails</a> does, by
unifying both the request and response cookies under one API, the CookieJar.</p>
<p>The <code>Cookie</code> property is a cookie jar. It can be treated as a map, where reads read the request
cookies and writes set response cookies:</p>
<pre class="prettyprint">
get("/get_and_set_cookies", \-> {
print( Cookies["Cookie1"] ) // reads the request cookie
Cookies["Cookie2"] = "Yay!" // writes a response cookie
// An advanced cookie write, setting timeout, secure and path as well
Cookies.set("Cookie3", "Yay!", Integer.MAX_INT, true, "/get_and_set_cookies)
})
</pre>
</section>
<a class="anchor" name="config"></a>
<section>
<h2>Configuration</h2>
<p>Although SparkGS is XML-free, there are a few properties you can use to configure your server via the
spark file</p>
<h3>Port</h3>
<p>The port of the server can be set via the <code>Port</code> property. SparkGS will also inspect the environment
and if a <code>$PORT</code> variable is set, it will use that as the default.</p>
<h3>Layout</h3>
<p>As was covered above in the <a href="#templates">Templates</a> section, you can set the layout by using the
<code>Layout</code> property.</p>
<h3>Static Files</h3>
<p>If you want SparkGS to serve your static assets as well, you can set the directory that they should be served
out of via the <code>StaticFiles</code> property:</p>
<pre class="prettyprint">
StaticFiles = "/public"
// route definitions...
</pre>
</section>
<a class="anchor" name="advanced_routing"></a>
<section>
<h2>Advanced Routing</h2>
<p>SparkGS gives you some additional route definition tools so you can easily adopt standard URL conventions in
your application.</p>
<h3>RESTful routing</h3>
<p>If you want to define a RESTful set of URLs, you can use the <code>resource()</code> method, which takes a
path string and controller object.</p>
<pre class="prettyprint">
resource("/contacts", new ContactsController())
</pre>
<p>The controller must implement the <code>IResourceController</code> interface. Following Rails conventions,
methods are mapped like so:</p>
<table class="table">
<thead>
<tr>
<td style="width:15%">
Verb + Route
</td>
<td>
Method
</td>
</tr>
</thead>
<tbody>
<tr>
<td>GET <em>root</em></td>
<td>index()</td>
</tr>
<tr>
<td>GET <em>root</em>/new</td>
<td>_new()</td>
</tr>
<tr>
<td>POST <em>root</em></td>
<td>create()</td>
</tr>
<tr>
<td>GET <em>root</em>/:id</td>
<td>show(id)</td>
</tr>
<tr>
<td>GET <em>root</em>/:id/edit</td>
<td>edit(id)</td>
</tr>
<tr>
<td>PUT <em>root</em>/:id <br/> POST <em>root</em>/:id</td>
<td>update(id)</td>
</tr>
</tbody>
</table>
<p>Additionally, all additional declared public methods on the controller are mapped to URLs like so:</p>
<ul>
<li>A declared public method <code>foo()</code> is made available at <code><em>root</em>/foo</code> for all HTTP verbs</li>
<li>A declared public method <code>bar(id:String)</code> is made available at <code><em>root</em>/:id/foo</code> for all
HTTP verbs</li>
</ul>
<p>Almost all controllers will want to implement <code>IHasRequestContext</code> so they can access request values</p>
<h3>RPC-style routing</h3>
<p>If you wish to simply expose a controllers methods RPC style, you can use the <code>rpc()</code> method:</p>
<pre class="prettyprint">
rpc("/rpc", new RPCController())
</pre>
<p>All declared public methods on the class will be made available in the following manner:</p>
<p>For a declared public method <code>methodN()</code> with parameters <code>p1,...,pN</code>, the path
<code><em>root</em>/methodN</code> will handle all HTTP verbs by invoking the method, converting the
string value of the HTTP parameter into the type of the the parameter to the method.</p>
<p>Note that RPC controllers do not need to implement any interface, but may want to implement <code>IHasRequestContext</code>
in order to get access to the request information.</p>
</section>
<a class="anchor" name="heroku"></a>
<section>
<h2>Deploying To Heroku</h2>
<p>
To deploy a SparkGS application on Heroku, you will need three additional files:
</p>
<ul>
<li>
A <code>/Procfile</code> file with the following content:
<pre class="prettyprint">
web: java -cp "target/dependency/*":target/classes sparkgs.Bootstrap
</pre>
</li>
<li>
A <code>/system.properties</code> file with the following content:
<pre class="prettyprint">
java.runtime.version=1.7
</pre>
</li>
<li>
A <code>/pom.xml</code> file with the following contents:
<pre class="prettyprint">
...
<dependency>
<groupId>org.gosu-lang.gosu</groupId>
<artifactId>sparkgs</artifactId>
<version>0.1.0</version>
</dependency>
...
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<filtering>false</filtering>
<directory>src/main/java</directory>
<includes>
<include>**/*.gs</include>
<include>**/*.gsp</include>
<include>**/*.gst</include>
<include>**/*.gsx</include>
</includes>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</pre>
</li>
</ul>
<p>With that done, you should be able to simply push to your heroku git URL, and the app will come up!</p>
</section>
<a class="anchor" name="examples"></a>
<section>
<h2>Examples</h2>
<p>Here is an example spark file, showing some basic route definitions:</p>
<pre class="prettyprint">
classpath "org.gosu-lang.gosu:sparkgs:0.10"
extends sparkgs.SparkFile
uses controller.*
uses view.*
uses view.layout.*
uses java.util.*
//------------------------------------
// Config
//------------------------------------
DefaultLayout = AppLayout // A Gosu Template layout
StaticFiles = "/public"
//------------------------------------
// Routes
//------------------------------------
// Root example using a writer
get("/", \-> Sample.render(Writer))
// Raw string example
get("/foo", "Foo!")
// Post example
post("/post_to", \-> Params['foo'] )
// Handle all verbs example
handle("/handle", \-> Request.IsGet )
// Handle specific verbs example
handle("/handle2", \-> Request.IsGet, :verbs = {GET, POST} )
// Redirect example
get("/redirect", \-> redirect("/foo") )
// REST-ful resource example
resource("/contacts", new ContactsController())
// RPC example
rpc("/rpc", new RPCExample())
// Custom layout example
get("/custom_layout", \-> {
Layout = CustomLayout
Writer.append("Check out my custom layout!")
})
// Cookie example
get("/cookie1", \-> {
Cookies["Foo"] = UUID.randomUUID() as String
redirect("/cookie2")
})
get("/cookie2", \-> Cookies["Foo"] )
// Header example
get("/header", \-> {
Headers["X-Foo"] = "Bar"
return "Sent the header 'X-Foo' along..."
})
</pre>
</section>
<div class="doc-bottom-spacer">
</div>