-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
2386 lines (2049 loc) · 139 KB
/
index.html
File metadata and controls
2386 lines (2049 loc) · 139 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
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML>
<html>
<head>
<title>Lightning Java Documentation</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="highlighter/styles/tomorrow-night.css">
<script src="js/jquery.min.js"></script>
<script src="highlighter/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
</head>
<body>
<div class="header">
<div class="wrapper">
<h1>Lightning Documentation</h1>
</div>
</div>
<div class="wrapper">
<div class="columns">
<div class="left">
<h2>Table of Contents</h2>
<ul id="table_of_contents"></ul>
</div>
<div class="right">
<!-- BEGIN RIGHT -->
<section>
<a name="info"></a>
<h2>Information</h2>
<p>Lightning is a simple yet expressive web framework for Java.</p>
<p>Our design goals are:</p>
<ul>
<li>To provide the convenience of save-and-refresh development in Java</li>
<li>To provide powerful debugging tools that speed up development</li>
<li>To include a built-in web server that allows developers to get set up quickly by writing purely Java</li>
<li>To provide APIs that are convenient and easy to learn for both beginners and professionals</li>
<li>To include the core functionality needed to build secure, scalable modern web applications</li>
<li>To give developers the freedom to use Lightning with the tools they know and love</li>
</ul>
<p>Built-In Features:</p>
<ul>
<li>Routing (w/ wildcards and parameters)</li>
<li>Path-Based Filters (w/ wildcards and parameters)</li>
<li>Templating (w/ Freemarker by default)</li>
<li>Emails (via SMTP)</li>
<li>SSL</li>
<li>MySQL (w/ connection pooling, transactions)</li>
<li>Sessions</li>
<li>Authentication</li>
<li>Multipart Requests/File Uploads</li>
<li>Form Validation</li>
<li>Async Handlers/Server-Sent Events</li>
<li>Web Sockets</li>
<li>HTTP2 & HTTP2C Support</li>
<li>Dependency Injection</li>
<li>Debug Mode</li>
<li>...and so much more!</li>
</ul>
<p><a href="https://github.com/lightning-framework/lightning" target="_blank">Source (GitHub)</a></p>
</section>
<section>
<a name="examples"></a>
<h2>Examples</h2>
<p>Lightning maintains an up to date repository containing code examples, tutorials, and a list of open source projects using the framework.</p>
<p><a href="https://github.com/lightning-framework/examples" target="_blank">Examples (GitHub)</a></p>
</section>
<section>
<a name="getting_started"></a>
<h2>Getting Started</h2>
<p>To use Lightning, simply create a new <a href="https://maven.apache.org/" target="_blank">Maven</a> project and add Lightning as a dependency. In order to use Lightning effectively, you need to follow Maven's <a href="https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html" target="_blank">directory structure conventions</a>. An example <code>pom.xml</code> is included below for your convenience.</p>
<div class="info-box">If you haven't used Maven before, don't fret! Maven is simply an automated dependency management system for Java that automatically downloads required packages from Maven Central and installs them into your project. All Maven projects contain a <code>pom.xml</code> file that specifies the needed dependencies. Most modern Java IDEs include built-in support for Maven.<br />
<br />
To get started in Eclipse, save the sample <code>pom.xml</code> below in a new folder. Then, choose File > Import > Existing Maven Project and browse to the folder you created. Maven will automatically download and install the dependencies you need to get started.
</div>
<div class="highlighter-rouge"><div class="file">/pom.xml</div><pre><code class="xml">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>your-group-id</groupId>
<artifactId>your-project-id</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>your-project-id</name>
<url>your-project-url</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>edu.rice.mschurr</groupId>
<artifactId>lightning</artifactId>
<version>0.0.2</version>
</dependency>
</dependencies>
</project>
</code></pre></div>
<div class="info-box">The latest version of Lightning may not always be available through Maven Central. You can download and utilize the latest version by cloning <a href="https://github.com/lightning-framework/lightning" target="_blank">lightning-framework/lightning</a> and running <code>mvn install</code> in the cloned folder. This will install the latest version locally onto your machine and allow you to use it in your projects as if it was available from Maven Central. For some IDEs (e.g. Eclipse), simply importing the downloaded Maven project folder into your IDE will be sufficient.</div>
<p>Next, you'll want to set up logging by adding a <code>logback.xml</code> file. See <a href="#logging">Logging</a> for an example logback file.</p>
<p>Next, you'll want to write a launcher for your application. You will run this launcher class in order to start the built-in web server.</p>
<div class="highlighter-rouge"><div class="file">/src/main/java/myapp/AppLauncher.java</div><pre><code class="java">
package myapp;
import lightning.Lightning;
import lightning.config.Config;
public final class AppLauncher {
public static void main(String[] args) throws Exception {
// Lightning requires some configuration information.
// In particular, we must tell it what package(s) should be scanned for routes.
Config config = new Config();
config.scanPrefixes = ImmutableList.of("myapp.controllers");
config.server.hmacKey = "SOMETHING LONG AND RANDOM!";
// Launch the server and block the thread until the server exits.
(new LightningServer(config)).start().join();
}
}
</code></pre></div>
<p>Next, let's add a controller that displays a simple hello world page.</p>
<div class="highlighter-rouge"><div class="file">/src/main/java/myapp/controllers/HomeController.java</div><pre><code class="java">
package myapp.controllers;
import lightning.ann.*;
import static lightning.enums.HTTPMethod.*;
import static lightning.server.Context.*;
@Controller
public final class HomeController {
@Route(path="/", methods={GET})
public void handleHomePage() throws Exception {
response().write("Hello World!");
}
}
</code></pre></div>
<p>To view your application, run <code>AppLauncher</code> and navigate to <a href="http://localhost/" target="_blank">http://localhost/</a> in the web browser of your choice.</p>
<div class="info-box">Lightning contains many additional configuration options. For example, you can set the <code>server.port</code> property to change the port that the built-in server binds to. See <a href="#config">here</a> for a complete list of available options. See <a href="#external">here</a> for instructions on integrating third-party databases and libraries with Lightning.</div>
<h3>Using an SQL Database</h3>
<p>First, you need to configure a database by modifying your launcher:</p>
<div class="highlighter-rouge"><div class="file">/src/main/java/myapp/AppLauncher.java</div><pre><code class="java">
...
public final class AppLauncher {
public static void main(String[] args) throws Exception {
...
// Add a MySQL database:
config.db.host = "localhost";
config.db.port = 3306;
config.db.user = "root";
config.db.pass = "root";
config.db.name = "lightning";
...
}
}
</code></pre></div>
<p>You can now issue queries in your controllers. Let's create an example table and add some data:</p>
<div class="highlighter-rouge"><div class="file">Schema (SQL)</div><pre><code class="sql">
CREATE TABLE tinyurls (
code varchar(255) not null,
url mediumtext not null,
user_id int(64) unsigned not null,
last_updated int(64) not null,
last_clicked int(64) not null default 0,
click_count int(64) not null default 0,
PRIMARY KEY(code),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
) CHARACTER SET utf8 COLLATE utf8_general_ci;
INSERT INTO tinyurls (code, url, user_id, last_updated, last_clicked, click_count)
VALUES ('facebook', 'http://www.facebook.com/', 0, 0, 0, 0);
INSERT INTO tinyurls (code, url, user_id, last_updated, last_clicked, click_count)
VALUES ('google', 'http://www.google.com/', 0, 0, 0, 0);
</code></pre></div>
<p>To retrieve this data in your app and format it as JSON you can issue a query:</p>
<div class="highlighter-rouge"><div class="file">/src/main/java/myapp/controllers/ListUrlsController.java</div><pre><code class="java">
package myapp.controllers;
import lightning.ann.*;
import static lightning.enums.HTTPMethod.*;
import static lightning.server.Context.*;
import java.util.*;
@Controller
public final class ListUrlsController {
@Route(path="/urls/json", methods={GET})
@Json // Returned object should be JSONified and written to response.
public Object handleListUrlsJson() throws Exception {
return fetchUrls();
}
public List<Map<String, Object>> fetchUrls() throws Exception {
List<Map<String, Object>> rows = new ArrayList<>();
try (NamedPreparedStatement query = db().prepare("SELECT * FROM tinyurls;")) {
try (ResultSet result = query.executeQuery()) {
while (result.next()) {
Map<String, Object> row = new HashMap<>();
row.put("code", result.getString("code"));
row.put("url", result.getString("url"));
rows.add(row);
}
}
}
return rows;
}
}
</code></pre></div>
<p>For more information, see the <a href="#sql">Lightning SQL Documentation</a>.</p>
<h3>Rendering Templates</h3>
<p>We can extend the above controller to display the URLs as HTML instead of JSON by using a template.</p>
<p>First, we need to configure templates by modifying the launcher:</p>
<div class="highlighter-rouge"><div class="file">/src/main/java/myapp/AppLauncher.java</div><pre><code class="java">
...
public final class AppLauncher {
public static void main(String[] args) throws Exception {
...
config.server.templateFilesPath = "myapp/templates";
...
}
}
</code></pre></div>
<p>Second, we need to modify the controller to render the template:</p>
<div class="highlighter-rouge"><div class="file">/src/main/java/myapp/controllers/ListUrlsController.java</div><pre><code class="java">
...
public final class ListUrlsController {
...
@Route(path="/urls", methods={GET})
@Template("urls.ftl")
public Object handleListUrls() throws Exception {
// Return the view model for urls.ftl.
return ImmutableMap.of("urls", fetchUrls());
}
...
}
</code></pre></div>
<p>Third, we need to write the template:</p>
<div class="highlighter-rouge"><div class="file">/src/main/resources/myapp/templates/urls.ftl</div><pre><code class="html">
<#escape x as x?html>
<#list urls as item>
${item.code} - ${item.url}<br />
</#list>
</#escape>
</code></pre></div>
<p>For more information, see the <a href="#templates">Lightning Templates Documentation</a>. You may also find the <a href="http://freemarker.org/" target="_blank">FreeMarker Documentation</a> useful. If you would prefer to use a template language other than FreeMarker, you may <a href="#config">configure</a> one.</p>
<h3>Static Files</h3>
<p>To serve static files, configure a root directory by modifying your launcher:</p>
<div class="highlighter-rouge"><div class="file">/src/main/java/myapp/AppLauncher.java</div><pre><code class="java">
...
public final class AppLauncher {
public static void main(String[] args) throws Exception {
...
// Specify a path in your project class path.
// Prefer using a relative path so that you can package to a JAR.
config.server.staticFilesPath = "myapp/static";
...
}
}
</code></pre></div>
<p>All static files will be served on their paths relative to the root directory you specify. For example, with the above configuration, you can place an image at <code>/src/main/resources/myapp/static/image.jpg</code> and it will be served on <a href="http://localhost/image.jpg">http://localhost/image.jpg</a>.</p>
<p>In production, Lightning will attempt to serve these files optimally (using memory-mapped buffers, in-memory caching, and HTTP caching). You may <a href="#config">configure</a> the behavior of these caches if you wish. In debug mode, all caching will be disabled to ensure that you receive the newest version of these files each time you refresh the page. In both modes, Lightning supports HTTP range queries.</p>
<h3>Handling Forms</h3>
<p>Consider the following web form for adding a new short URL:</p>
<div class="highlighter-rouge"><div class="file">Form</div><pre><code class="html">
<form action="http://localhost/urls/add" method="POST">
Code: <input type="text" name="code" />
URL: <input type="text" name="url" />
<input type="submit" value="Add URL" />
</form>
</code></pre></div>
<p>You might write a handler for this form as follows:</p>
<div class="highlighter-rouge"><div class="file">/src/main/java/myapp/controllers/AddUrlController.java</div><pre><code class="java">
package myapp.controllers;
import lightning.ann.*;
import lightning.enums.HTTPStatus;
import static lightning.enums.HTTPMethod.*;
import static lightning.server.Context.*;
...
public final class AddUrlController {
@Route(path="/urls/add", methods={POST})
public void handleAddUrl() throws Exception {
validate("code").isLongerThan(2)
.isShorterThan(6)
.isAlphaNumericDashUnderscore();
validate("url").isURL();
if (passesValidation()) {
db().transaction(() -> {
try (NamedPreparedStatement q1 =
db().prepare("SELECT * FROM tinyurls WHERE code = :code;")) {
q1.setString("code", queryParam("code").stringValue());
try (ResultSet r1 = q1.executeQuery()) {
// Abort and show a 400 page if a row exists.
badRequestIf(r1.next(), "Provided code already in use.");
}
}
db().prepareInsert("tinyurls", ImmutableMap.of(
"url", queryParam("url").stringValue(),
"code", queryParam("code").stringValue(),
"user_id", 0,
"last_updated", Time.now()
)).executeUpdateAndClose();
});
response().write("Your URL has been saved.");
} else {
// Abort and show a 400 page.
badRequest(validator().getErrorsAsString());
}
}
}
</code></pre></div>
<h3>Speeding Up Development</h3>
<p>You should enable debug mode to speed up your development process. You can do this by modifying your launcher:</p>
<div class="highlighter-rouge"><div class="file">/src/main/java/myapp/AppLauncher.java</div><pre><code class="java">
public final class AppLauncher {
public static void main(String[] args) throws Exception {
...
config.enableDebugMode = true;
config.autoReloadPrefixes = config.scanPrefixes;
...
}
}
</code></pre></div>
<p>After enabling debug mode, you should find:</p>
<ul>
<li>Your code changes take immediate effect when you refresh the page in your browser without the need to restart the server (save-and-refresh development). In order for this feature to work, you'll need to make sure your IDE is configured to recompile automatically when you save.</li>
<li>You will see in-browser stack traces when one of your route handlers throws an exception. In production, you would simply see a generic 500 Internal Server Error page which reveals no information about the error.</li>
<li>You can see a route overview on <a href="http://localhost/~lightning/routes">http://localhost/~lightning/routes</a>.</li>
</ul>
<a href="images/debugscreen.png" target="_blank" class="image-frame">
<img src="images/debugscreen.png" />
<span>An in-browser debug stack trace.</span>
</a>
<a href="images/routeoverview.png" target="_blank" class="image-frame">
<img src="images/routeoverview.png" />
<span>The in-browser route overview.</span>
</a>
<p>For more details, see <a href="#debug">debug mode</a>.</p>
<h3>Users and Authentication</h3>
<p>You may provide your own third-party system for managing users, sessions, and authentication (see <a href="#external">Using External Tools</a>), or, for convenience, you may choose to utilize the ones provided by Lightning (see <a href="#auth">Authentication</a>).</p>
</section>
<section>
<a name="config"></a>
<h2>Configuration</h2>
<p>Lightning is configured primarily by building an instance of <code>lightning.config.Config</code> and passing that instance to <code>Lightning::launch</code>. You can create the needed <code>Config</code> instance any way you like! You can build it in code, parse it from a file, parse it from command-line flags, or any combination thereof. Our goal is to provide maximum customizability and a production-ready web server. As such, we have included a huge number of configuration options with sensible defaults to let you get started quickly and scale when you need to. At this time, <code>scanPrefixes</code> and <code>server.hmacKey</code> are the only mandatory configuration options. The server will fail to start and display an error in cases where your configuration is invalid.</p>
<div class="info-box">A subset of configuration options are documented here. The complete documentation for configuration options can be found in the source code of <code><a href="https://github.com/lightning-framework/lightning/blob/master/src/main/java/lightning/config/Config.java" target="_blank">Config.java</a></code>.</div>
<p>A few components of Lightning are configured via <a href="#deps_injection">dependency injection</a>. In particular, the drivers for sessions, users, groups, auth, templates, json, and the cache are configured this way (see below).</p>
<p>Information about configuring various components of the framework follows.</p>
<h3>Debug Mode</h3>
<p>To enable debug mode, set <code>enableDebugMode</code> to <code>true</code>.</p>
<p>To enable automatic code reloading in debug mode, set <code>autoReloadPrefixes</code> to a list of Java package prefixes whose code can be <strong>safely</strong> reloaded while the server is running. See <a href="#debug">Debug Mode</a> for more info.</p>
<h3>Cookies</h3>
<p>To enable the <a href="#cookies">Cookies API</a>, set <code>server.hmacKey</code> to a secret private key.</p>
<h3>Static Files</h3>
<p>To enable static file serving, set <code>server.staticFilesPath</code> to a path relative to <code>${project}/src/main/resources</code>. File paths will be mapped directly to web server paths (for example, <code>image.jpg</code> in the folder you specify will be served on <code>/image.jpg</code>).</p>
<p>Additional configuration options are available to control the performance of static file caching; see <code>Config.java</code> for more information.</p>
<h3>SSL</h3>
<p>To use SSL, you must place your certificate in a Java Key Store (JKS) and then set the <code>ssl</code> options. You can find information on how to place your certificates (.pem/.crt) into a Java Key Store (.jks) in the <a href="http://www.eclipse.org/jetty/documentation/current/configuring-ssl.html" target="_blank">Jetty Documentation: Configuring SSL</a>.</p>
<h3>HTTP/2</h3>
<p>To enable HTTP/2, set <code>server.enableHttp2</code> to <code>true</code>. <strong>Be sure to read the documentation on this configuration option!</strong> You may need to use a custom JVM boot path to use HTTP/2. HTTP/2 requires you to enable SSL.</p>
<h3>Multipart Support</h3>
<p>To enable HTTP multipart support, set <code>server.multipartEnabled</code> to <code>true</code>. <code>server</code> includes additional multipart-related options that you may wish to configure (e.g. to limit maximum request size). You might want to upload the temporary files location and thresholds at which parts are flushed to disk.</p>
<h3>Template Drivers</h3>
<p>To utilize templates, set <code>server.templateFilesPath</code> to a path relative to <code>${project}/src/main/resources</code>.</p>
<p>You may bind a dependency injection for <code>lightning.templates.TemplateEngine</code> to utilize a custom template engine. By default, FreeMarker is used.</p>
<h3>JSON Drivers</h3>
<p>You may bind a dependency injection for <code>lightning.json.JsonService</code> to utilize a custom JSON engine. By default, Google's <code>gson</code> library is used.</p>
<h3>Cache Drivers</h3>
<p>You must bind a dependency injection form <code>lightning.cache.CacheDriver</code> to utilize the <a href="#caches">Cache APIs</a>. By default, the Cache APIs will be unavailable.</p>
<h3>Mail</h3>
<p>Configure the <code>mail</code> property to set up access to an SMTP server or enable the logging mail driver in order to use the <a href="#email">Mail APIs</a>.</p>
<h3>MySQL</h3>
<p>Configure the <code>db</code> property to set up access to a MySQL database.</p>
<h3>Session Drivers</h3>
<p>No custom configuration is available at this time. By default, an SQL driver is used on the database configured in the <code>db</code> property. Make sure you have installed the <a href="https://github.com/lightning-framework/lightning/tree/master/src/main/resources/lightning/schema" target="_blank">schema</a> needed.</p>
<h3>User Drivers</h3>
<p>No custom configuration is available at this time. By default, an SQL driver is used on the database configured in the <code>db</code> property. Make sure you have installed the <a href="https://github.com/lightning-framework/lightning/tree/master/src/main/resources/lightning/schema" target="_blank">schema</a> needed.</p>
<h3>Group Drivers</h3>
<p>No custom configuration is available at this time. By default, an SQL driver is used on the database configured in the <code>db</code> property. Make sure you have installed the <a href="https://github.com/lightning-framework/lightning/tree/master/src/main/resources/lightning/schema" target="_blank">schema</a> needed.</p>
<h3>Auth Drivers</h3>
<p>No custom configuration is available at this time. By default, an SQL driver is used on the database configured in the <code>db</code> property. Make sure you have installed the <a href="https://github.com/lightning-framework/lightning/tree/master/src/main/resources/lightning/schema" target="_blank">schema</a> needed.</p>
<h3>Custom Options</h3>
<p>You can extend <code>lightning.config.Config</code> if you wish to add custom configuration options specific to your application.</p>
<div class="highlighter-rouge"><div class="file">/src/main/java/myapp/config/MyAppConfig.java</div><pre><code class="java">
package myapp.config;
import lightning.config.Config;
public final class MyAppConfig extends Config {
public int myOption;
public String myOtherOption = "DEFAULT";
}
</code></pre></div>
<p>You will still be able to pass your subclass to <code>Lightning::launch</code> thanks to the magic of polymorphism. If you wish to access your custom configuration options in your controllers, you should set up <a href="#deps_injection">dependency injection</a> for your custom configuration type:</p>
<div class="highlighter-rouge"><pre><code class="java">
injector.bindClassToInstance(MyAppConfig.class, config);
</code></pre></div>
</section>
<section>
<a name="debug"></a>
<h2>Debug Mode</h2>
<p>To enable debug mode, <a href="#config">configure</a> <code>enableDebugMode</code> to <code>true</code> in your application launcher.</p>
<p>There are cases where you may wish to change the behavior of your application code when debug mode is enabled (for example, to disable caching done by your code). You can check (in your code) if debug mode is enabled by invoking <code>context().isDebug()</code> on <code>lightning.server.Context</code>.</p>
<p>Debug mode <strong><em>should never</em></strong> be enabled in production deployments as it exposes system internals and severely degrades scalability since it disables most caching.</p>
<p>In order to use debug mode, you must:</p>
<ul>
<li>Configure <code>projectRootPath</code> to point to your project's root folder (where <code>pom.xml</code> is)</li>
<li>Follow Maven directory structure conventions (place code in <code>src/main/java</code> and resources in <code>src/main/resources</code>)</li>
</ul>
<p>If you enable debug mode, the following features will be activated:</p>
<ul>
<li>Enables automatic hot reloading of all Java classes defined within a package specified in your configured <code>autoReloadPrefixes</code> by using a custom class loader. This includes all routes, exception handlers, filters, etc. defined within those packages. In order for this feature to work, you will want to configure your IDE to automatically recompile on save. <strong>It is important to understand that not all code can be safely reloaded!</strong> You should make sure that you understand the limitations of <a href="https://www.toptal.com/java/java-wizardry-101-a-guide-to-java-class-reloading" target="_blank">Java Class Loading</a> (outlined below). In particular, types for which you have configured <a href="#deps_injection">dependency injection</a> and any code in <code>lightning.*</code> or any of its dependencies may not be reloaded safely.</li>
<li>Enables displaying exception stack traces and debug information in-browser. This debug page will show <em>instead of</em> the generic 500 Internal Server error page generated by the framework that you would see in production (with debug mode disabled). You may add additional search paths for code snippets by specifying <code>codeSearchPaths</code>. Keep in mind that finding code snippets is an imperfect art (there's a decent amount of guess work involved since the compiled Java byte code available at runtime does not contain all of the information available in the source code).</li>
<li>Enables displaying template errors in-browser</li>
<li>Disables all caching of static files (both server-side and client-side)</li>
<li>Disables all caching of template files</li>
<li>Enables an interactive in-browser route overview on the configured <code>debugRouteMapPath</code></li>
</ul>
<p>Please keep in mind that errors are <em>always</em> <a href="#logging">logged</a> regardless of whether or not debug mode is enabled.</p>
<p>You may use debug mode when deployed to a JAR. You will not be able to reload code when deployed to a JAR, but you will still be able to get stack traces (though note the code snippets will be absent unless you correctly configure <code>projectRootPath</code> or package the source files into the JAR).</p>
<a href="images/debugscreen.png" target="_blank" class="image-frame">
<img src="images/debugscreen.png" />
<span>An in-browser debug stack trace.</span>
</a>
<a href="images/routeoverview.png" target="_blank" class="image-frame">
<img src="images/routeoverview.png" />
<span>The in-browser route overview.</span>
</a>
<h3>Code Hot-Swapping</h3>
<p>The ability to reload code changes without restarting the web server is one of Lightning's most powerful features. This feature brings the save-and-refresh style development that has made languages like PHP and Python popular for web development to Java. In order to use this feature, you will need to enable debug mode and configure <code>autoReloadPrefixes</code>. To make best use of this feature, you will want to use an IDE or build process that automatically recompiles Java class files when you save code changes. Eclipse and IntelliJ both support this functionality.</p>
<p><code>autoReloadPrefixes</code> specifies a list of package prefixes. Java code in packages beginning with these prefixes is assumed to be <em>safe to reload</em> on each incoming request. Java code in packages not beginning with these prefixes will not be reloaded on each incoming request - instead, a version of the code in these packages captured near JVM start-up time will be used for all requests.</p>
<h3>Understanding Class Loading and Limitations</h3>
<p>Each <code>.java</code> code file is compiled into one or more <code>.class</code> files (one per Java class). Each <code>.class</code> file contains the metadata and byte code for a single Java class that can be loaded and executed by a Java Virtual Machine (JVM).<p>
<p>Each class is loaded into memory by the JVM through a <code>java.lang.ClassLoader</code> the first time that it is referenced by byte code execution on the JVM. The code for a class is fixed (snapshotted) at the time it is loaded into memory by a class loader. Once created, these snapshots cannot be changed. Each <code>java.lang.Class</code> maintains a reference to the <code>ClassLoader</code> that loaded it into memory (e.g. <code>MyClass.class.getClassLoader()</code>). The default <code>ClassLoader</code> (<code>ClassLoader.getSystemClassLoader()</code>) in the JVM simply looks in the environment class path (specified in command line via <code>java -cp</code>) for the corresponding <code>.class</code> file on disk and loads it into memory. Static initialization of a class (static variables and <code>static { ... }</code> blocks) occurs at the time the class is loaded into memory.</p>
<p>It is possible to have more than one <code>ClassLoader</code> active in the JVM at a time. In fact, by creating a new <code>ClassLoader</code> instance after making code changes, we can obtain a newer (more recent) snapshot of the code for a class by invoking <code>loadClass</code> manually on the new <code>ClassLoader</code> instance.</p>
<p>To fully understand class loading, it is critical to understand a few things:</p>
<ul>
<li>For each <code>ClassLoader</code>, subsequent calls to <code>loadClass</code> with the same class name must return the same <code>Class</code> snapshot (instance) that previous calls returned</li>
<li>For each <code>ClassLoader</code>, when <code>loadClass</code> is invoked with a class name that has not yet been observed by that <code>ClassLoader</code>, the <code>ClassLoader</code> may either load a new snapshot of the <code>Class</code> from disk or delegate to a snapshot (instance) of the <code>Class</code> created by a different <code>ClassLoader</code>.</li>
<li><code>ClassLoader</code>s do not replace code that was previously loaded, but may instead elect to create a new snapshot (instance) of the same <code>Class</code>. Thus, there may exist many distinct, type-incompatible <code>Class</code>es with the same canonical name loaded into the JVM at the same time (up to one instance per <code>ClassLoader</code>).</li>
<li>When the JVM encounters a reference to a class that has not yet been loaded <em>by the same class loader that was used to load the code containing the reference</em>, <code>loadClass</code> is invoked on that class loader to obtain a snapshot of the reqiured class (possibly from disk) before continuing execution.</li>
</ul>
<p>This is how Lightning implements automatic code reloading - not through replacement, by loading and executing newer snapshots of the code. For each incoming request, Lightning creates a new class loader which acquires new snapshots for <em>only</em> the classes contained in <code>autoReloadPrefixes</code>.</p>
<p>For the classes not contained in <code>autoReloadPrefixes</code>, the new class loader <em>delegates</em> to <code>loadClass</code> on the system class loader. This means that <em>at most one snapshot</em> will exist for classes designated as non-reloadable. Further, static initialization will occur <em>at most once</em> for those classes. In other words, static state <em>will</em> persist through code reloads on classes designated as non-reloadable but <em>will not</em> persist through code reloads on classes designated as reloadable.</p>
<p>In practice, this makes it acceptable for reloadable classes to reference non-reloadable classes as it is assumed that the code for non-reloadable classes will not change during the lifetime of the JVM. On the other hand, it is not recommended for non-reloadable classes to reference reloadable classes since that reference will be fixed to the snapshot captured by the system class loader - a snapshot that will not be updated until the JVM is restarted.</p>
<p>Our recommendation is to enable code hot-swapping for all of your application code except:</p>
<ul>
<li>The class containing your <code>main</code> method (the launcher)</li>
<li>Any classes which are dependency injected</li>
<li>Any classes which are referenced by non-reloadable code</li>
<li>Any classes defined by Lightning (<code>lightning.*</code>) or its dependencies</li>
<li>Any classes which cannot be modified during runtime (e.g. Maven dependencies)</li>
</ul>
<p>NOTE: Reloading dependency-injectable classes is problematic because, for example, <code>InjectorModule::bindClassToInstance</code> called by your launcher binds the <code>Class</code> object loaded by the system class loader to an instance of that class with the code snapshot created by the system class loader. If that class is marked as reloadable, then, when your reloadable code wishes to inject an instance of that class, the reloadable code is not referencing the version of the class loaded by the system class loader, but a version loaded by a more recently created class loader. Even though that type and the type for which you installed the dependency injection binding have the same canonical name, the JVM treats them as distinct types referring to different snapshots of the code. Therefore, they are not type compatible. Consequently, Lightning's dependency injector will be unable to resolve an object for that parameter and will throw an exception at runtime.</p>
</section>
<section>
<a name="logging"></a>
<h2>Logging</h2>
<p>Lightning uses <a href="http://www.slf4j.org/" target="_blank">SLF4J</a> for logging implemented by <a href="http://logback.qos.ch/" target="_blank">logback</a>.</p>
<p>We recommend using logback to configure SLF4J to display logs in your console. A simple way to achieve this is adding a <code>logback.xml</code> file to your project classpath. We recommend setting the logging level to <code>INFO</code> for lightning classes and <code>DEBUG</code> for your classes.</p>
<div class="highlighter-rouge"><div class="file">/src/main/resources/logback.xml</div><pre><code class="xml">
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>
</encoder>
</appender>
<logger name="myapp" level="DEBUG" />
<logger name="lightning" level="INFO" />
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
</code></pre></div>
</section>
<section>
<a name="deps_injection"></a>
<h2>Dependency Injection</h2>
<p>Lightning allows you to inject custom dependencies into most areas of the framework. This includes exception handlers, route handlers, web sockets, filters, initializers, finalizers, controller constructors - almost any method or constructor that is invoked by the framework.</p>
<p>To configure dependency injection, you must build an <code>InjectorModule</code> and pass the module to <code>Lightning::launch</code> in your launcher.</p>
<p>An <code>InjectorModule</code> allows you to specifies objects that you would like to be injectable. You may specify injections either by (a) their type, (b) the presence of a custom annotation on a parameter, or (c) the presence of a <code>@Inject(name)</code> annotation on the parameter. An example of configuring each of the types of injection is below:</p>
<div class="highlighter-rouge"><div class="file">AppLauncher.java</div><pre><code class="java">
import lightning.config.Config;
import lightning.inject.InjectorModule;
import lightning.Lightning;
class AppLauncher {
public static void main(String[] args) throws Exception {
Config config = ...;
InjectorModule injector = new InjectorModule();
injector.bindClassToInstance(MyDependency.class, new MyDependency());
injector.bindNameToInstance("MyDependency", new MyDependency());
injector.bindAnnotationToInstance(MyAnnotation.class, new MyDependency());
(new LightningServer(config, injector)).start().join();
}
}
</code></pre></div>
<p>You may utilize the injected dependencies by inserting them as arguments to any injectable constructor or method. An example is below:</p>
<div class="highlighter-rouge"><div class="file">MyController.java</div><pre><code class="java">
import lightning.ann.*;
@Controller
public final class MyController {
@Route(path="/a", method={GET})
public void handleA(MyDependency dep) throws Exception {
// dep is the instance bound via bindClassToInstance.
}
@Route(path="/b", method={GET})
public void handleB(@MyAnnotation MyDependency dep) throws Exception {
// dep is the instance bound via bindAnnotationToInstance.
}
@Route(path="/c", method={GET})
public void handleC(@Inject("MyDependency") MyDependency dep) throws Exception {
// dep is the instance bound via bindNameToInstance.
}
}
</code></pre></div>
<p>In addition to custom dependency injection, Lightning will automatically configure dependency injection for global framework types (like <code>Config</code>) and request-specific types where applicable (like <code>Request</code> and <code>Response</code>).</p>
<p>It is not possible to inject custom request-specific objects; only global objects can be injected at this time. If you need request-specific objects, inject a thread-safe factory or <code>ThreadLocal</code> factory that produces those objects.</p>
<p>There's no limit to how many injectable arguments a method can have so long as each argument can be resolved by the dependency injector. The framework will throw an exception if it encounters a method for which it cannot resolve all parameters.</p>
<p>It is not possible to dependency inject instances of classes whose code is flagged to automatically reload while running in <a href="#debug">debug mode</a>.</p>
<h3>Framework-Provided Injectable Types</h3>
<p>The follow global objects are available for dependency injection:</p>
<table class="grid">
<tr><td><code>lightning.config.Config</code> (equivalent to invoking <code>config()</code>)</td></tr>
<tr><td><code>lightning.mail.Mailer</code> (equivalent to invoking <code>mail()</code>)</td></tr>
<tr><td><code>lightning.templates.TemplateEngine</code></td></tr>
<tr><td><code>lightning.cache.Cache</code> (equivalent to invoking <code>cache()</code>)</td></tr>
<tr><td><code>lightning.json.JsonService</code></td></tr>
<tr><td><code>lightning.db.MySQLDatabaseProvider</code> (connection pool, only if <a href="#config">configured</a>)</td></tr>
<tr><td>Any types you install in the <code>InjectorModule</code> passed to Lightning by your launcher</td>
</table>
<p>The following request-specific objects are available for dependency injection (where applicable):</p>
<table class="grid">
<tr><td><code>lightning.http.Request</code> (equivalent to invoking <code>request()</code>)</td></tr>
<tr><td><code>lightning.http.Response</code> (equivalent to invoking <code>response()</code>)</td></tr>
<tr><td><code>lightning.sessions.Session</code> (equivalent to invoking <code>session()</code>)</td></tr>
<tr><td><code>lightning.mvc.Validator</code> (equivalent to invoking <code>validator()</code>)</td></tr>
<tr><td><code>lightning.mvc.URLGenerator</code> (equivalent to invoking <code>url()</code>)</td></tr>
<tr><td><code>lightning.groups.Groups</code> (equivalent to invoking <code>groups()</code>)</td></tr>
<tr><td><code>lightning.users.Users</code> (equivalent to invoking <code>users()</code>)</td></tr>
<tr><td><code>lightning.users.User</code> (equivalent to invoking <code>user()</code>)</td></tr>
<tr><td><code>lightning.db.MySQLDatabase</code> (equivalent to invoking <code>db()</code>)</td></tr>
<tr><td><code>lightning.mvc.HandlerContext</code> (instance version of <code>lightning.server.Context</code>)</td></tr>
<tr><td><code>lightning.websockets.WebSocketHandlerContext</code> (instance version of <code>lightning.websockets.WebSocketContext</code>)</td></tr>
<tr><td><code>javax.servlet.http.HttpServletRequest</code> (equivalent to invoking <code>request().raw()</code>)</td></tr>
<tr><td><code>javax.servlet.http.HttpServletResponse</code> (equivalent to invoking <code>response().raw()</code>)</td></tr>
<tr><td><code>lightning.cache.Cache</code> (equivalent to invoking <code>cache()</code>)</td></tr>
<tr><td><code>org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest</code> (only in <a href="#websockets">Web Sockets</a>)</td></tr>
<tr><td><code>org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse</code> (only in <a href="#websockets">Web Sockets</a>)</td></tr>
</table>
<h3>Query/Route Parameter Injection</h3>
<p>You may annotate a parameter with <code>@QParam(name)</code> to inject the value of the query parameter with the given name. Similarly, you may annotate a parameter with <code>@RParam(name)</code> to inject the value of the route parameter with the given name. The annotated parameter must have type <code><a href="#parameters">Param</a></code>, <code>String</code>, <code>int/Integer</code>, <code>long/Long</code>, <code>double/Double</code>, or <code>float/Float</code>. The framework will automatically attempt to convert the provided parameter to the given type. If a conversion cannot be performed or if the query/route parameter is not present, a <code>BadRequestException</code> will be thrown.</p>
</section>
<section>
<a name="lifecycle"></a>
<h2>Life Cycle, Threading, & Context</h2>
<p>Lightning uses a thread-per-request model. For each incoming HTTP request or web socket event, a thread is allocated from the server thread pool to service that request/event to completion.</p>
<p>A HTTP request will be allocated only a single thread throughout it's lifetime. Thus, you may safely use the static methods defined on <code>lightning.server.Context</code> to access resources allocated to the incoming request in a thread-safe manner.</p>
<p>The allocated thread is released back into the pool upon returning from the request or event handler, whether normally or exceptionally, after the framework has completed any neccesary finalizing actions (such as rendering a template or error page).</p>
<p>The general life-cycle for an incoming HTTP request is:</p>
<ol>
<li>Allocate a thread to the request</li>
<li>Match the request to a route</li>
<li>Invoke any path-based before filters</li>
<li>Allocate a new controller instance</li>
<li>Invoke initializers on the controller</li>
<li>Invoke the matched controller method</li>
<li>Invoke any finalizers on the controller</li>
<li>Finalize the response and clean-up resources</li>
<li>Release the thread back into the pool</li>
</ol>
<p>Invoking <code>halt()</code> is a simple way to skip to step (8) from any previous step in the process. Similarly, if an exception is thrown anywhere in the process, the framework will skip to step (8) and the finalizing action will instead be to render an error page (or invoke a <a href="#exceptions">custom exception handler</a> if installed).</p>
<p>Please keep in mind that <a href="#async">async request handlers</a> do not follow these semantics but, instead, conform to the semantics specified by the Java Servlet API for asynchronous handlers. In particular, resources allocated to the request <em>are not</em> cleaned up upon returning from the handler, but will instead be cleaned up at a later point in time when the <code>AsyncContext</code> is closed.</p>
</section>
<section>
<a name="controllers"></a>
<h2>Controllers</h2>
<p>A <em>controller</em> is simply a class that can handle incoming HTTP requests. All controllers must be annotated with (or have a parent annotated with) <a href="https://github.com/lightning-framework/lightning/blob/master/src/main/java/lightning/ann/Controller.java" target="_blank"><code>@Controller</code></a> and must be located within the scan prefixes specified in the <a href="#config">configuration</a> provided to Lightning.</p>
<p>Controller classes must be declared public and may have up to one public constructor. A controller's constructor is <a href="#deps_injection">injectable.</a></p>
<p>Controllers may have initializers (methods annotated with <a href="https://github.com/lightning-framework/lightning/blob/master/src/main/java/lightning/ann/Initializer.java" target="_blank"><code>@Initializer</code></a>), finalizers (methods annotated with <a href="https://github.com/lightning-framework/lightning/blob/master/src/main/java/lightning/ann/Finalizer.java" target="_blank"><code>@Finalizer</code></a>), and <a href="#routes">routes</a> (methods annotated with <a href="https://github.com/lightning-framework/lightning/blob/master/src/main/java/lightning/ann/Route.java" target="_blank"><code>@Route</code></a>). Initializers, finalizers, and routes are all <a href="#deps_injection">injectable</a>.</p>
<p>When an incoming request matches a route specified on a controller, a new instance of the controller class is allocated. All initializers are invoked by the controller, followed by the matched route method, followed by all finalizers.</p>
<p>Initializers and finalizers are inherited by child classes. If a controller has multiple initializers/finalizers, the order in which they execute is undefined.</p>
<p>Initializers must be public, must return void, and may throw exceptions. If an initializer throws an exception, further processing of the request is halted (including any unexecuted initializers and the matched route - finalizers still execute).</p>
<p>Finalizers must be public, must return void, and may throw exceptions. All finalizers will always execute. Finalizers serve the purpose of destructors and may be reliably used for resource clean-up. Finalizer exceptions are suppressed and will only be visible via the <a href="#logging">SLF4J log</a>.</p>
<div class="highlighter-rouge"><div class="file">MyController.java</div><pre><code class="java">
import lightning.ann.*;
import static lightning.enums.HTTPMethod.*;
import static lightning.server.Context.*;
@Controller
public class MyController {
@Initializer
public void init() {
// Save any references needed via dependency injection.
}
@Finalizer
public void finalize() {
// Clean up any allocated resources.
}
@Route(path="/a", methods={GET})
public void handleRouteA() throws Exception {
response().write("A");
}
@Route(path="/b", methods={GET})
public void handleRouteB() throws Exception {
response().write("B");
}
}
</code></pre></div>
</section>
<section>
<a name="routing"></a>
<h2>Routing</h2>
<p>Pre-Requisite Article: <em><a href="#controllers">Controllers</a></em></p>
<p>A <em>route</em> specifies particular code that should execute when the path and method of an incoming HTTP request matches those specified by the route. In Lightning, routes are specified by annotating public instance methods on <a href="#controllers">Controllers</a> with the <a href="https://github.com/lightning-framework/lightning/blob/master/src/main/java/lightning/ann/Route.java" target="_blank"><code>@Route</code></a> annotation.</p>
<p>Routes will automatically be installed based on the presence of the annotations and can safely be automatically reloaded in debug mode. The path matcher will attempt to match paths to static files before trying to match routes.</p>
<h3>Routing Path Format</h3>
<p>Routing paths may contain <em>parameters</em> and/or <em>wildcards</em>.</p>
<p>A parameter matches a single path segment. Parameters are indicated by prefixing a path segment with <code>:</code>. The remaining portion of the path segment after the <code>:</code> specifies the parameter's name. Paths may have zero or more parameters.</p>
<p>A wildcard matches one or more path segments. Wildcards are indicated by setting a path segment to <code>*</code>. Wildcards may only be present in the last segment in the provided path. Thus, paths may have zero or one wildcard.</p>
<p>Here are some valid example routing paths:</p>
<ul>
<li><code>/</code></li>
<li><code>/my/path/</code></li>
<li><code>/u/:username</code></li>
<li><code>/r/:subreddit</code></li>
<li><code>/t/:topic/c/:comment</code></li>
<li><code>/account/*</code></li>
<li><code>*</code> (matches everything)</li>
</ul>
<p>Wildcards and parameters for the matched route will be exposed on the <a href="#request">Request</a> object.</p>
<h3>Routing Conflicts & Resolution</h3>
<p>Routing conflicts are allowed in cases where the set of matched paths is not entirely overlapping. For example, routes for both <code>*</code> and <code>/a/b</code> are allowed (on the same HTTP method). Routes for both <code>/:a/:b</code> and <code>/:b/:a</code> are not. The presence of conflicting routes will prevent server start-up and display an error in terminal.</p>
<p>Routes are implemented using a bastardized version of a radix tree. Routing is <code>O(n)</code> with respect to the number of characters in the request path regardless of the number of routes installed.</p>
<p>Routes are resolved by crawling the routing radix tree by path segments searching for a match with priority given to exact matches, then parametric matches, then wildcard matches.</p>
<p>As a concrete example, consider a server with the following set of routes installed:</p>
<ul>
<li><code>/</code></li>
<li><code>/something</code></li>
<li><code>/*</code></li>
<li><code>/:something</code></li>
<li><code>/u/:something</code></li>
<li><code>/u/*</code></li>
<li><code>/z/:something</code></li>
</ul>
<p>For the above routes, the given request URLs will be matched as follows:</p>
<table class="grid">
<thead>
<tr>
<th>Request Path</th>
<th>Route Pattern</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/</code></td>
<td><code>/</code></td>
</tr>
<tr>
<td><code>/something</code></td>
<td><code>/something</code></td>
</tr>
<tr>
<td><code>/anything</code></td>
<td><code>/:something</code></td>
</tr>
<tr>
<td><code>/anything/more</code></td>
<td><code>/*</code></td>
</tr>
<tr>
<td><code>/u</code></td>
<td><code>/:something</code></td>
</tr>
<tr>
<td><code>/u/h</code></td>
<td><code>/u/:something</code></td>
</tr>
<tr>
<td><code>/u/h/z</code></td>
<td><code>/u/*</code></td>
</tr>
<tr>
<td><code>/z/h/u</code></td>
<td><code>/*</code></td>
</tr>
</tbody>
</table>
<h3>Route Handlers</h3>
<p>Route handlers must be <em>public, instance methods</em> defined on a <code><a href="#controllers">@Controller</a></code> located within the scan prefixes specified in Lightning's <a href="#config">config</a>. Route handlers must be annotated with <code>@Route</code> which specifies the path(s) and method(s) for which the handler will be invoked. A single method may be annotated with <code>@Route</code> multiple times.</p>
<p>A route handler may return a value. If a route handler chooses to return a value, the framework will take an action using the return value:</p>
<table class="grid">
<thead>
<tr>
<th>Return Type</th>
<th>Framework Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>void</code></td>
<td>No Action.</td>
</tr>
<tr>
<td><code>null</code></td>
<td>No Action.</td>
</tr>
<tr>
<td><code>String</code></td>
<td>Given string will be written to response body as HTML.</td>
</tr>
<tr>
<td><code>ModelAndView</code></td>
<td>Given model and view will be rendered to response body as HTML.</td>
</tr>
<tr>
<td><code>File</code></td>
<td>Given file will be served as if it were a static file.</td>
</tr>
<tr>
<td>Any other type</td>
<td>
IF THE HANDLER IS ANNOTATED WITH <code>@Json</code>:<br />
Returned value will be JSONified and written to response<br />
<br />
IF THE HANDLER IS ANNOTATED WITH <code>@Template(name)</code>:<br />
Returned value will be used as view model to render template with given name to the response<br />
<br />
OTHERWISE:<br />
An exception will be thrown by the framework (by default, an internal server error).
</td>
</tr>
</tbody>
</table>
<p>Route handlers may throw exceptions. The framework will catch any thrown exceptions and <a href="#exceptions">handle them</a>.</p>
<p>Route handlers are <a href="#deps_injection">injectable</a> with both request-specific and global objects and therefore may accept any number of injectable arguments.</p>
<p>During the execution of a route handler, the value of any matched parameters and wildcards will be available on the <a href="#requests"><code>request()</code></a>.</p>
</section>
<section>
<a name="path_filters"></a>
<h2>Path-Based Filters</h2>
<p>You may specify code snippets that execute before route handlers on certain path patterns.</p>
<p>An example path-based filter which filters unauthenticated requests follows:</p>
<div class="highlighter-rouge"><div class="file">AccessControlFilters.java</div><pre><code class="java">
import lightning.ann.Before;
import lightning.http.AccessViolationException;
import lightning.http.NotAuthorizedException;
import static lightning.enums.FilterPriority.*;
import static lightning.enums.HTTPMethod.*;
import static lightning.server.Context.*;
public final class AccessControlFilters {
@Before(path="/admin/*", methods={GET, POST}, priority=HIGH)
public static void adminFilter() throws Exception {
if (!user().hasPrivilege(Privilege.ADMIN)) {
// AccessViolationException triggers an HTTP 403 Forbidden page
// by default (unless you add a custom exception handler).
throw new AccessViolationException();
}
}
@Before(path="/admin/*", methods={GET, POST}, priority=HIGHEST)
@Before(path="/account/*", methods={GET, POST}, priority=HIGHEST)
public static void authFilter() throws Exception {
if (!auth().isLoggedIn()) {
// NotAuthorizedException triggers an HTTP 401 Unauthorized page
// by default (unless you add a custom exception handler).
throw new NotAuthorizedException();
}
}
}
</code></pre></div>
<p>Please keep in mind...</p>
<ul>
<li>Path-based filters must be defined on classes within the scan prefixes specified in the <a href="#config">configuration</a> provided to Lightning</li>
<li>A filter method may be annotated with <code>@Before</code> multiple times</li>
<li>You may control the order in which filters execute by changing the priority in the annotation. Filters with higher priority will execute first. Filters with the same priority may execute in any order.</li>
<li>Path-based filter methods are <a href="#deps_injection">injectable</a> with both global and request-specific objects</li>
<li>Path-based filters can safely use <code>lightning.server.Context</code></li>
<li>Path-based filters will only execute if the given method and path also match a <a href="#routing">route</a>. They will not execute for static files or not founds.</li>
<li>Path-based filters can take advantage of both wildcard paths and parameterized paths (same path format as <a href="#routing">routes</A>). The parameters and wildcards will be available on the <code><a href="#requests">request()</a></code> within the filter function.</li>
<li>Filter matching is highly performant (porportional mostly to the length of the request path and the number of matched filters).</li>
<li>Path-based filters may prevent further request processing by invoking <code>halt()</code> or throwing an exception (see <a href="#lifecycle">Life Cycle</a>).</li>
</ul>
<p>For more information about path-based filters, see <a href="https://github.com/lightning-framework/lightning/blob/master/src/main/java/lightning/ann/Before.java" target="_blank">@Before</a>.</p>
</section>
<section>
<a name="exceptions"></a>
<h2>Exception Handlers</h2>
<p>You may specify code that should execute when a route handler or filter throws a <code>Throwable</code> of a given type. The exception handler will also match all subclasses of the given type for which a more specific handler is not installed. For example, installing an exception handler for <code>Throwable.class</code> will catch every exception.</p>
<p>When writing exception handlers, please keep in mind that the framework does not buffer output unless you explicitly enable output buffering in your <a href="#config">configuration</a>. Thus, depending on how your application is written, HTTP headers and some of the request body may already have been sent to the client before an exception is thrown and the corresponding exception handler is invoked (for example, if a controller throws an exception in the middle of its execution).</p>
<p>An exception handler is specified by annotating a public static method on a class located within the scan prefixes specified in your <a href="#config">configuration</a> with <code>@Exceptionhandler</code>. Exception handlers must return void but may accept any number of <a href="#deps_injection">injectable</a> (global and request-specific) arguments. In addition, the causing exception will be dependency-injectable. Exception handlers may throw exceptions. If an exception handler throws an exception, the default framework exception handler will be executed.</p>
<div class="highlighter-rouge"><div class="file">ExampleExceptionHandler.java</div><pre><code class="java">
import lightning.ann.*;
import lightning.http.*;
import static lightning.server.Context.*;
// This class must be defined in the scan prefixes in your config.
public final class ExampleExceptionHandler {
// Specify that this method performs exception handling for the given exception type
// (and all subclasses thereof unless a more specific exception handler is installed
// for a subclass).
@ExceptionHandler(NotFoundException.class)
public static void handleException(NotFoundException e) throws Exception {
response().status(404);
response().write("404 Not Found");
halt();
}
}
</code></pre></div>
<p>Things to keep in mind:</p>
<ul>
<li>Attempting to install multiple exception handlers for the same exception type will prevent server start-up</li>
<li>Exception handlers may be used to log information or to render an error page</li>
<li>Exception handlers may re-throw the causing exception to trigger the default exception handler</li>
<li>Exception handlers may be used to <a href="#custom_errors">replace framework error pages</a></li>
<li>Exceptions are always <a href="#logging">logged</a> (even if you don't install a handler)</li>
<li>Exception handlers may use <code>lightning.server.Context</code></li>
<li>Exception handlers <em>should</em> re-throw the exception when <a href="#debug">debug mode</a> is configured to pass the exception to the built-in exception handler which renders the in-browser stack traces (if you do not do this, you will not get stack traces for the registered exception type)</li>
</ul>
</section>
<section>
<a name="custom_errors"></a>
<h2>Custom Error Pages</h2>
<p>Pre-Requisite Article: <em><a href="#exceptions">Exception Handlers</a></em></p>
<p>Lightning includes built-in exceptions that are thrown when certain errors occur and ships with default handlers for these exceptions. For example, <code>lightning.http.NotFoundException</code> is thrown whenever a request path does not match either a route/static file or when a controller directly throws a <code>NotFoundException</code>. The default exception handler for <code>NotFoundException</code> renders a generic 404 Not Found page. A full list of built-in exceptions and their default handler actions can be found below.</p>
<p>All other exceptions (e.g. custom exceptions) are handled by a built-in exception handler for <code>Throwable.class</code> which generates a very generic 500 Internal Server Error page (in production) or shows the debug stack trace if <a href="#debug">debug mode</a> is configured.</p>
<p>You may override the framework default error pages by adding <a href="#exceptions">exception handlers</a> for all of the built-in exceptions and a catch-all handler for <code>Throwable.class</code> that renders a generic error page.</p>
<p><strong>Recommendation:</strong> All of your non-HTTP exception handlers should re-throw the causing exception when <a href="#debug">debug mode</a> is configured. This will pass the exception to the built-in handler for <code>Throwable.class</code> which will then render the in-browser stack trace. If you do not re-throw the exception when debug mode is enabled, you will not be able to see in-browser stack traces.</p>
<p>The following HTTP exceptions are shipped with Lightning and render generic error pages:</p>
<table class="grid">
<thead>
<tr>
<th>Exception (<code>lightning.http</code>)</th>
<th>HTTP Code</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AccessViolationException</code></td>
<td>403 Forbidden</td>
</tr>
<tr>
<td><code>BadRequestException</code></td>
<td>400 Bad Request</td>
</tr>
<tr>
<td><code>MethodNotAllowedException</code></td>
<td>405 Method Not Allowed</td>
</tr>
<tr>
<td><code>NotAuthorizedException</code></td>
<td>401 Unauthorized</td>
</tr>
<tr>
<td><code>NotFoundException</code></td>
<td>404 Not Found</td>
</tr>
<tr>
<td><code>NotImplementedException</code></td>
<td>501 Not Implemented</td>
</tr>
</tbody>
</table>
<p>An example of replacing some error pages with your own:</p>
<div class="highlighter-rouge"><div class="file">CustomExceptionHandler.java</div><pre><code class="java">
import lightning.ann.*;
import lightning.http.*;
import static lightning.server.Context.*;
public final class CustomExceptionHandler {
@ExceptionHandler(Throwable.class)
public static void handleException(Throwable e) throws Exception {
renderErrorPage(500,
'Internal Server Error',
'An error occurred servicing your request. Please try again.');
}
@ExceptionHandler(NotFoundException.class)
public static void handleException(NotFoundException e) throws Exception {
renderErrorPage(404,
'Not Found',
'The resource you requested does not exist.');
}
private static void renderErrorPage(int code, String title, String text) throws Exception {
if (config().enableDebugMode) {
throw e; // Re-throw to see stack traces.
}
response().status(code);
render('http_error.ftl', // You need to make this template.
ImmutableMap.of(
'code': code,
'title': title,
'text': text
));
}
}
</code></pre></div>
</section>
<section>
<a name="web_sockets"></a>