ÎÒÓùýServlets¡¢JSP¡¢JAX-RS¡¢
Spring¿ò¼Ü¡¢Play¿ò¼Ü¡¢´øFaceletsµÄJSFÒÔ¼°Spark Framework¡£ÔÚÎÒ¿´À´£¬ÕâЩ¿ò¼Ü²¢Ã»ÓкܺõØÊµÏÖÃæÏò¶ÔÏóÉè¼Æ¡£ËüÃdzä³âמ²Ì¬·½·¨¡¢Î´¾²âÊÔµÄÊý¾Ý½á¹¹ÒÔ¼°²»¹»ÃÀ¹ÛµÄ½â¾ö·½Ê½¡£Òò´ËÒ»¸öÔÂǰÎÒ¾ö¶¨¿ªÊ¼±àд×Ô¼ºµÄJava
Web¿ò¼Ü£¬ÎÒÖÆ¶¨ÁËһЩ»ù±¾µÄÐÅÌõ£º1) ûÓÐNULL£¬2) ûÓÐpublic static·½·¨£¬3)
ûÓпɱäÀࣨmutable class£©£¬4) ûÓÐÀàÐÍת»»¡¢·´ÉäºÍinstanceof²Ù×÷¡£ÕâËÄÌõ»ù±¾×¼ÔòÓ¦¸Ã×ã¹»±£Ö¤¸É¾»µÄ´úÂëºÍ͸Ã÷µÄ¼Ü¹¹¡£Õâ¾ÍÊÇTakes¿ò¼Üµ®ÉúµÄÔÒò¡£ÈÃÎÒÃÇ¿´¿´ÕâÊÇÈçºÎʵÏֵġ£
Java Web¼Ü¹¹¼ò½é
¼òµ¥À´Ëµ£¬Õâ¾ÍÊÇÎÒ¶ÔÒ»¸öWebÓ¦Óüܹ¹ÒÔ¼°Æä×é¼þµÄÀí½â¡£
Ê×ÏÈ£¬Òª´´½¨Ò»¸öWeb·þÎñÆ÷£¬ÎÒÃÇÓ¦¸Ãд´½¨Ò»¸öÍøÂçÌ×½Ó×Ö£¨socket£©£¬Æä½«»áÔÚÌØ¶¨µÄTCP¶Ë¿Ú½ÓÊÜÁ¬½ÓÇëÇó¡£Í¨³£Õâ¸ö¶Ë¿ÚÊÇ80£¬µ«ÊÇΪÁË·½±ã²âÊÔÎÒ½«Ê¹ÓÃ8080¶Ë¿Ú¡£ÕâЩÔÚJavaÖÐÓÃServerSocketÀàÍê³É¡£
import java.net.ServerSocket; public class Foo { public static void main(final String... args) throws Exception { final ServerSocket server = new ServerSocket(8080); while (true); } } |
ÕâЩ×㹻ȥÆô¶¯Ò»¸öWeb·þÎñÆ÷¡£ÏÖÔÚ£¬socketÒѾ¾ÍÐ÷¼àÌý8080¶Ë¿Ú¡£µ±ÓÐÈËÔÚä¯ÀÀÆ÷´ò¿ª http://localhost:8080
£¬½«»á½¨Á¢Á¬½Ó²¢ÇҵȴýµÄ³ÝÂÖÔÚä¯ÀÀÆ÷Éϲ»Í£µÄÐýת¡£±àÒëÕâЩƬ¶ÎÊÔһϡ£ÎÒÃǸոÕûÓÐʹÓÃÈκοò¼Ü´î½¨ÁËÒ»¸ö¼òµ¥µÄWeb·þÎñÆ÷¡£ÎÒÃDz¢Ã»ÓжԽøÈëµÄÁ¬½Ó×öÈκÎÊÂÇ飬µ«ÊÇҲûÓоܾøËüÃÇ¡£ËùÓеÄÁ¬½Ó¶¼ÕýÔÚ·þÎñÆ÷¶ÔÏóÄÚ²¿ÅŶӡ£ÕâЩÔÚºǫ́Ïß³ÌÖÐÍê³É£¬Õâ¾ÍÊÇΪʲôÐèÒªÔÚ×îºó·ÅÒ»¸ö
while(true) µÄÔÒò¡£Ã»ÓÐÕâ¸öÎÞÏÞÑ»·£¬Ó¦Óý«»áÁ¢¼´ÖÕÖ¹²Ù×÷²¢ÇÒ·þÎñÆ÷Ì×½Ó×Ö½«»á¹Ø±Õ¡£
ÏÂÒ»²½ÊǽÓÊܽøÈëµÄÁ¬½Ó¡£ÔÚJavaÖУ¬Í¨¹ý¶Ô accept() ·½·¨µÄ×èÈûµ÷ÓÃÀ´Íê³É¡£
final Socket socket = server.accept(); |
Õâ¸ö·½·¨½«»áÒ»Ö±×èÈûÏ̵߳ȴýÖ±µ½Ò»¸öеÄÁ¬½Óµ½´ï¡£ÐÂÁ¬½ÓÒ»·¢Éú£¬accept() ·½·¨¾Í»á·µ»ØÒ»¸öSocketʵÀý¡£ÎªÁ˽ÓÊÜÏÂÒ»¸öÁ¬½Ó£¬ÎÒÃǽ«»áÔٴε÷ÓÃ
accept() ·½·¨¡£Òò´Ë¼òµ¥À´½²£¬ÎÒÃǵÄWeb·þÎñÆ÷½«»áÏñÏÂÃæÒ»Ñù¹¤×÷£º
public class Foo { public static void main(final String... args) throws Exception { final ServerSocket server = new ServerSocket(8080); while (true) { final Socket socket = server.accept(); // 1. Read HTTP request from the socket // 2. Prepare an HTTP response // 3. Send HTTP response to the socket // 4. Close the socket } } } |
ÕâÊǸöÎÞÏÞÑ»·¡£²»¶Ï½ÓÊÜеÄÁ¬½ÓÇëÇó£¬Ê¶±ðÇëÇó¡¢´´½¨ÏìÓ¦¡¢·µ»ØÏìÓ¦£¬È»ºóÔٴνÓÊÕеÄÁ¬½Ó¡£HTTPÐÒéÊÇÎÞ״̬µÄ£¬ÕâÒâζ×Å·þÎñÆ÷²»Ó¦¸Ã¼ÇסÏÈǰÈκÎÒ»¸öÁ¬½Ó·¢ÉúÁËʲô¡£ËüËù¹ØÐĵÄÊÇÔÚÌØ¶¨Á¬½ÓÖд«ÈëµÄHTTPÇëÇó¡£
HTTPÇëÇóÀ´×ÔÓÚÌ×½Ó×ÖµÄÊäÈëÁ÷ÖУ¬¾ÍÏñ¶àÐеÄÎı¾¿é¡£Õâ¾ÍÊÇÄã¶ÁÈ¡Ì×½Ó×ÖµÄÊäÈëÁ÷½«»á¿´µ½µÄÄÚÈÝ£º
final BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); while (true) { final String line = reader.readLine(); if (line.isEmpty()) { break; } System.out.println(line); } |
Ä㽫»á¿´µ½ÒÔÏÂÐÅÏ¢£º
GET / HTTP/1.1 Host: localhost:8080 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36 Accept-Encoding: gzip, deflate, sdch Accept-Language: en-US,en;q=0.8,ru;q=0.6,uk;q=0.4 |
¿Í»§¶Ë£¨ÀýÈç¹È¸èµÄChromeä¯ÀÀÆ÷£©°ÑÕâЩÎı¾´«¸øÒѽ¨Á¢µÄÁ¬½Ó¡£ËüÁ¬½Ó±¾µØµÄ8080¶Ë¿Ú£¬Ö»ÒªÁ¬½ÓÍê³É£¬Ëü»áÁ¢¼´½«ÕâЩÎı¾·¢¸ø·þÎñÆ÷£¬È»ºóµÈ´ýÏìÓ¦¡£
ÎÒÃǵŤ×÷¾ÍÊÇÓôÓÇëÇóµÃµ½µÄÐÅÏ¢´´½¨ÏàÓ¦µÄHTTPÏìÓ¦¡£Èç¹ûÎÒÃǵķþÎñÆ÷·Ç³£Ôʼ£¬¿ÉÒÔºöÂÔÇëÇóÖеÄËùÓÐÐÅÏ¢¶ø¶ÔËùÓеÄÇëÇó½ö½ö·µ»Ø¡°Hello,
world! ¡±£¨¼òµ¥Æð¼ûÎÒÓÃÁËIOUtils£©¡£
import java.net.Socket; import java.net.ServerSocket; import org.apache.commons.io.IOUtils; public class Foo { public static void main(final String... args) throws Exception { final ServerSocket server = new ServerSocket(8080); while (true) { try (final Socket socket = server.accept()) { IOUtils.copy( IOUtils.toInputStream("HTTP/1.1 200 OK/r/n/r/nHello, world!"), socket.getOutputStream() ); } } } } |
¾ÍÊÇÕâÑù¡£µ±·þÎñÆ÷¾ÍÐ÷£¬ÊÔ×űàÒëËüÅÜÆðÀ´¡£ÈÃä¯ÀÀÆ÷Ö¸Ïòhttp://localhost:8080£¬Ä㽫»á¿´µ½¡°Hello,
world!¡±¡£
$ javac -cp commons-io.jar Foo.java $ java -cp commons-io.jar:. Foo & $ curl http://localhost:8080 -v * Rebuilt URL to: http://localhost:8080/ * Connected to localhost (::1) port 8080 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.37.1 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK * no chunk, no close, no size. Assume close to signal end < * Closing connection 0 Hello, world! |
Õâ¾ÍÊÇÄã±àÒëweb·þÎñÆ÷Òª×öµÄËùÓÐÊÂÇé¡£ÏÖÔÚÈÃÎÒÃÇÀ´ÌÖÂÛÈçºÎÈÃËüÃæÏò¶ÔÏó²¢ÇÒ¿É×é¼þ»¯¡£ÈÃÎÒÃÇ¿´¿´Takes¿ò¼ÜÊÇÈçºÎ½¨Á¢µÄ¡£
·ÓÉ/·Ö·¢
×îÖØÒªµÄÒ»²½ÊǾö¶¨ËÀ´¸ºÔð¹¹½¨HTTPÏìÓ¦¡£Ã¿¸öHTTPÇëÇó¶¼ÓÐ1£©Ò»¸ö²éѯ£¬2£©Ò»¸ö·½·¨£¬3£©Ò»Ð©Í·²¿ÐÅÏ¢¡£ÒªÊ¹ÓÃÕâÈý¸ö²ÎÊý£¬ÐèҪʵÀý»¯Ò»¸ö¶ÔÏóÀ´ÎªÎÒÃǹ¹½¨ÏìÓ¦¡£ÔÚ´ó¶àÊýµÄWeb¿ò¼ÜÖУ¬Õâ¸ö¹ý³Ì½Ð×öÇëÇó·Ö·¢»ò·ÓÉ¡£ÏÂÃæÊÇÈçºÎÓÃTakesÍê³ÉÕâЩ¡£
final Take take = takes.route(request); final Response response = take.act(); |
»ù±¾ÉÏÓÐÁ½²½¡£µÚÒ»²½´Ótakes´´½¨TakeµÄʵÀý£¬µÚ¶þ²½´Ótakes´´½¨ÏìÓ¦µÄʵÀý¡£ÎªÊ²Ã´²ÉÓÃÕâÖÖ·½Ê½£¿Ö÷ÒªÊÇΪÁË·ÖÀëÔðÈΡ£TakesµÄʵÀý¸ºÔð·Ö·¢ÇëÇó²¢ÇÒ³õʼ»¯ÕýÈ·µÄTake£¬TakeµÄʵÀý¸ºÔð´´½¨ÏìÓ¦¡£
ÓÃTakes´´½¨Ò»¸ö¼òµ¥µÄÓ¦Óã¬ÄãÓ¦¸Ã´´½¨Á½¸öÀà¡£Ê×ÏÈ£¬Ò»¸öʵÏÖTakes½Ó¿ÚµÄÀࣺ
import org.takes.Request; import org.takes.Take; import org.takes.Takes; public final class TsFoo implements Takes { @Override public Take route(final Request request) { return new TkFoo(); } } |
ÎÒÃÇ·Ö±ðÓÃTsºÍTkµÄǰ׺´ú±íTakesºÍTake¡£µÚ¶þ¸öÄãÓ¦¸Ã´´½¨µÄÀ࣬һ¸öʵÏÖTake½Ó¿ÚµÄÀࣺ
import org.takes.Take; import org.takes.Response; import org.takes.rs.RsText; public final class TkFoo implements Take { @Override public Response act() { return new RsText("Hello, world!"); } } |
ÏÖÔÚµ½Æô¶¯·þÎñÆ÷µÄʱºòÁË£º
import org.takes.http.Exit; import org.takes.http.FtBasic; public class Foo { public static void main(final String... args) throws Exception { new FtBasic(new TsFoo(), 8080).start(Exit.NEVER); } } |
FtBasicÀàÕýÊÇʵÏÖÁËÉÏÃæ½âÊ͹ýµÄºÍsocketÒ»ÑùµÄ²Ù×÷¡£ËüÔÚ¶Ë¿Ú8080ÉÏÆô¶¯Ò»¸ö·þÎñÆ÷¶ËµÄsocket£¬Í¨¹ý´«¸ø¹¹Ô캯ÊýTsFooʵÀýÀ´·Ö·¢ËùÓнøÈëµÄÁ¬½Ó¡£ËüÔÚÒ»¸öÎÞÏÞÑ»·ÖÐÍê³É·Ö·¢£¬ÓÃExitʵÀýÿÃë¼ì²éÊÇ·ñÊÇʱºòÍ£Ö¹¡£ÏÔÈ»£¬Exit.NEVER×ÜÊÇ·µ»Ø¡°Ç벻Ҫֹͣ¡±¡£
HTTPÇëÇó
ÏÖÔÚÈÃÎÒÃÇÀ´Á˽âһϵ½´ïTsFooµÄHTTPÇëÇóÄÚ²¿¶¼ÓÐʲô£¬ÎÒÃÇÄÜ´ÓÇëÇóÖеõ½Ê²Ã´¡£ÏÂÃæÊÇÔÚTakesÖж¨ÒåµÄRequest½Ó¿Ú£º
public interface Request { Iterable<String> head() throws IOException; InputStream body() throws IOException; } |
ÇëÇó·ÖΪÁ½²¿·Ö£ºÍ·²¿ºÍÕýÎÄ¡£¸ù¾ÝRFC 2616ÖÐHTTP¹æ·¶£¬Í·²¿°üº¬ÓÃÀ´¿ªÊ¼ÕýÎĵĿÕÐÐǰµÄËùÓеÄÐС£¿ò¼ÜÖÐÓкܶàÓÐÓõÄÇëÇó×°ÊÎÆ÷¡£ÀýÈ磬RqMethod¿ÉÒÔ°ïÖú´ÓÍ·²¿µÚÒ»ÐÐÈ¡µ½·½·¨Ãû¡£
final String method = new RqMethod(request).method(); |
RqHrefÓÃÀ´°ïÖúÌáÈ¡²éѯ²¿·Ö²¢ÇÒ½øÐнâÎö¡£ÀýÈ磬ÏÂÃæÊÇÒ»¸öÇëÇó£º
GET /user?id=123 HTTP/1.1 Host: www.example.com |
´úÂ뽫»áÌáÈ¡µÃµ½¡°123¡±£º
GET /user?id=123 HTTP/1.1 Host: www.example.com |
RqPrint¿ÉÒÔ»ñÈ¡Õû¸öÇëÇó»òÕßÕýÎÄ£¬×÷Ϊ×Ö·û´®´òÓ¡³öÀ´£º
final String body = new RqPrint(request).printBody(); |
ÕâÀïµÄÏë·¨ÊDZ£³ÖÇëÇó½Ó¿Ú¼òµ¥£¬²¢ÇÒÓÃ×°ÊÎÆ÷Ìṩ½âÎöÇëÇóµÄ¹¦ÄÜ¡£Ã¿Ò»¸ö×°ÊÎÆ÷¶¼·Ç³£Ð¡ÇÉÎȶ¨£¬Ö»ÓÃÀ´Íê³ÉÒ»¼þÊ¡£ËùÓÐÕâЩװÊÎÆ÷¶¼ÔÚ¡°org.takes.rq¡±°üÖС£Äã¿ÉÄÜÒѾÀí½â£¬¡°Rq¡±Ç°×º´ú±íÇëÇó£¨Request£©¡£
µÚÒ»¸öÕæÕýµÄWebÓ¦ÓÃ
ÈÃÎÒÃÇ´´½¨ÎÒÃǵÚÒ»¸öÕæÕýÒâÒåÉϵÄWebÓ¦Óã¬Ëü½«»á×öһЩÓÐÒâÒåµÄÊÂÇé¡£ÎÒÍÆ¼öÒÔÒ»¸öEntryÀ࿪ʼ¡£¶ÔJavaÀ´Ëµ£¬´ÓÃüÁîÐÐÆô¶¯Ò»¸öÓ¦ÓÃÊDZØÐëµÄ¡£
import org.takes.http.Exit; import org.takes.http.FtCLI; public final class Entry { public static void main(final String... args) throws Exception { new FtCLI(new TsApp(), args).start(Exit.NEVER); } } |
Õâ¸öÀàÖ»°üº¬Ò»¸ö¾²Ì¬ main() º¯Êý£¬´ÓÃüÁîÐÐÆô¶¯Ó¦ÓÃʱJVM½«»áµ÷ÓÃÕâ¸ö·½·¨¡£ÈçÄãËù¼û£¬ÊµÀý»¯
FtCLI£¬´«½øÒ»¸öTsAppÀàµÄʵÀýºÍÃüÁîÐвÎÊý¡£ÎÒÃǽ«»áÁ¢¿Ì´´½¨TsApp¶ÔÏó¡£FtCLI£¨·Òë³É¡°front-end
with command line interface¡±¼´¡°´øÃüÁîÐнӿڵÄǰ¶Ë¡±£©´´½¨ÁËFtBasicµÄʵÀý£¬ÓÃһЩÓÐÓõÄ×°ÊÎÆ÷¶ÔËü½øÐаü×°²¢¸ù¾ÝÃüÁîÐвÎÊýÅäÖá£ÀýÈ磬¡°¨Cport=8080¡±½«»áת»»³É8080¶Ë¿ÚºÅ²¢±»µ±×ö
FtBasic ¹¹Ô캯ÊýµÄµÚ¶þ¸ö²ÎÊý´«Èë¡£
webÓ¦Óñ¾Éí¼Ì³ÐTsWrap£¬½Ð×öTsApp£º
import org.takes.Take; import org.takes.Takes; import org.takes.facets.fork.FkRegex; import org.takes.facets.fork.TsFork; import org.takes.ts.TsWrap; import org.takes.ts.TsClasspath; final class TsApp extends TsWrap { TsApp() { super(TsApp.make()); } private static Takes make() { return new TsFork( new FkRegex("/robots.txt", ""), new FkRegex("/css/.*", new TsClasspath()), new FkRegex("/", new TkIndex()) ); } } |
ÎÒÃǽ«ÂíÉÏÌÖÂÛTsForkÀà¡£
Èç¹ûÄãÕýÔÚʹÓÃMaven£¬ÄãÓ¦¸Ã´ÓÕâ¸öpom.xml¿ªÊ¼£º
<?xml version="1.0"?> <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>foo</groupId> <artifactId>foo</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.takes</groupId> <artifactId>takes</artifactId> <version>0.9</version> <!-- check the latest in Maven Central --> </dependency> </dependencies> <build> <finalName>foo</finalName> <plugins> <plugin> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/deps</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> </build> </project> |
ÔËÐС° mvn clean package¡±»áÔÚ¡°target ¡±Ä¿Â¼ÖÐÉú³ÉÒ»¸ö foo.jar Îļþ²¢ÇÒÔÚ¡°target/deps¡±Ä¿Â¼Éú³ÉÒ»ÅúËùÓÐJARÒÀÀµ°ü¡£ÏÖÔÚÄã¿ÉÒÔ´ÓÃüÁîÐÐÔËÐÐÓ¦Óãº
$ mvn clean package $ java -Dfile.encoding=UTF-8 -cp ./target/foo.jar:./target/deps/* foo.Entry --port=8080 |
Ó¦ÓÃÒѾ¾ÍÐ÷£¬Äã¿ÉÒÔ²¿Êðµ½Heroku¡£ÔÚ²Ö¿âµÄ¸ùĿ¼Ï´´½¨Ò»¸öProfileÎļþ£¬È»ºó°Ñ²Ö¿âÍÆÈëHeroku¡£ÏÂÃæÊÇProfileµÄÄÚÈÝ£º
web: java -Dfile.encoding=UTF-8 -cp target/foo.jar:target/deps/* foo.Entry --port=${PORT} |
TsFork
TsForkÀà¿´ÉÏÈ¥ÊÇÆäÖÐÒ»¸ö¿ò¼ÜºËÐÄÔªËØ¡£Ëü½«½øÈëµÄHTTPÇëÇó·Óɵ½ÕýÈ·µÄ¡°take¡±¡£ËüµÄÂß¼·Ç³£µÄ¼òµ¥£¬´úÂëÒ²Ö»ÓÐÉÙÁ¿ÐС£Ëü·â×°ÁË¡°forks¡±µÄÒ»¸ö¼¯ºÏ£¬¡°forks¡±ÊÇFork<Take>½Ó¿ÚµÄʵÀý¡£
public interface Fork<T> { Iterator<T> route(Request req) throws IOException; } |
½öÓÐµÄ route() ·½·¨·µ»Ø¿Õµü´úÆ÷»òÕߺ¬Óе¥¸ötakeµÄµü´úÆ÷¡£TsFork±éÀúËùÓеÄforks£¬µ÷ÓÃËüÃǵÄ
route() ·½·¨Ö±µ½ÆäÖÐÒ»¸ö·µ»Øtake¡£Ò»µ©·¢Éú£¬TsFork»á°ÑÕâ¸ötake·µ»Ø¸øµ÷ÓÃÕߣ¬¼´ FtBasic¡£
ÏÖÔÚÎÒÃÇ×Ô¼ºÀ´´´½¨Ò»¸ö¼òµ¥µÄfork¡£ÀýÈ磬µ±ÇëÇóURL¡°/status¡±Ê±£¬ÎÒÃÇÏëչʾӦÓõÄ״̬¡£ÒÔÏÂÊÇ´úÂëʵÏÖ£º
final class TsApp extends TsWrap { private static Takes make() { return new TsFork( new Fork.AtTake() { @Override public Iterator<Take> route(Request req) { final Collection<Take> takes = new ArrayList<>(1); if (new RqHref(req).href().path().equals("/status")) { takes.add(new TkStatus()); } return takes.iterator(); } } ); } } |
ÎÒÏàÐÅÕâÀïµÄÂß¼ÊÇÇåÎúµÄ¡£ÒªÃ´·µ»ØÒ»¸ö¿Õµü´úÆ÷£¬ÒªÃ´·µ»ØÄÚ²¿°üº¬TKStatusʵÀýµÄµü´úÆ÷¡£Èç¹û·µ»Ø¿Õµü´úÆ÷£¬TsFork½«³¢ÊÔÔÚ¼¯ºÏÖÐѰÕÒÁíÒ»¸öÕâÑùµÄfork£¬Ëü¿ÉÒÔ»ñµÃTakeµÄʵÀý´Ó¶ø½øÐÐÏìÓ¦¡£Ë³±ãÌáһϣ¬Èç¹ûʲôҲû·¢ÏÖËùÓеÄforks·µ»Ø¿Õµü´úÆ÷£¬ÄÇôTsFork½«Å׳ö¡°Page
not found¡±µÄÒì³£¡£
ÕâÑùµÄÂ߼ͨ¹ý½Ð×öFkRegexµÄ¿ªÏä¼´ÓÃforkʵÏÖ£¬³¢ÊÔÓÃÌṩµÄͨÓñí´ïʽȥƥÅäÇëÇóµÄURI£º
final class TsApp extends TsWrap { private static Takes make() { return new TsFork( new FkRegex("/status", new TkStatus()) ); } } |
ÎÒÃÇ¿ÉÒÔ×éºÏ¶à²ã½á¹¹µÄTsForkÀ࣬ÀýÈ磺
final class TsApp extends TsWrap { private static Takes make() { return new TsFork( new FkRegex( "/status", new TsFork( new FkParams("f", "json", new TkStatusJSON()), new FkParams("f", "xml", new TkStatusXML()) ) ) ); } } |
Again, I believe it¡¯s obvious. The instance of FkRegex
will ask an encapsulated instance of TsFork to return
a take, and it will try to fetch it from one that FkParams
encapsulated. If the HTTP query is /status?f=xml, an
instance of TkStatusXML will be returned.
ÎÒÏàÐÅÂß¼ÊǺÜÇåÎúµÄ¡£FkRegexµÄʵÀý½«»áÒªÇóTsForkµÄ·âװʵÀý·µ»ØÒ»¸ötake£¬²¢ÇÒËü»á³¢ÊÔ´ÓFkParams·â×°µÄʵÀýÖлñÈ¡¡£
HTTPÏìÓ¦
ÏÖÔÚÈÃÎÒÃÇÌÖÂÛHTTPÏìÓ¦µÄ½á¹¹ÒÔ¼°ËüµÄÃæÏò¶ÔÏóµÄ³éÏ󡪡ª Response¡£ÒÔÏÂÊǽӿڵ͍Ò壺
public interface Response { Iterable<String> head() throws IOException; InputStream body() throws IOException; } |
ºÍRequest¿´ÆðÀ´·Ç³£ÀàËÆ£¬ÊDz»ÊÇ£¿ºÃ°É£¬ËüÊÇÏàͬµÄ¡£ÒòΪHTTPÇëÇóºÍÏìÓ¦µÄ½á¹¹¼¸ºõÊÇÏàͬµÄ£¬Î¨Ò»µÄÇø±ðÖ»ÊǵÚÒ»ÐС£ÓкܶàÓÐÓõÄ×°ÊÎÆ÷°ïÖú¹¹½¨ÏìÓ¦¡£ËûÃÇÊÇ×é¼þ»¯µÄ£¬ÕâʹµÃʹÓÃÆðÀ´·Ç³£·½±ã¡£ÀýÈ磬Èç¹ûÄãÏë¹¹½¨Ò»¸ö°üº¬HTMLÒ³ÃæµÄÏìÓ¦£¬Äã¿ÉÒÔÕâÑù×ö£º
final class TkIndex implements Take { @Override public Response act() { return new RsWithStatus( new RsWithType( new RsWithBody("<html>Hello, world!</html>"), "text/html" ), 200 ); } } |
ÔÚÕâ¸öʾÀýÖУ¬RsWithBody×°ÊÎÆ÷´´½¨ÏìÓ¦µÄÕýÎÄ£¬µ«ÊÇûÓÐÍ·²¿¡£È»ºóRsWithType ¸øÏìÓ¦Ìí¼Ó¡°
Content-Type: text/html¡±Í·²¿¡£½Ó×ÅRsWithStatusÈ·±£ÏìÓ¦µÄµÚÒ»Ðаüº¬¡°HTTP/1.1
200 OK¡±¡£
Äã¿ÉÒÔ¸´ÓÃÒÑÓеÄ×°ÊÎÆ÷À´´´½¨×Ô¼ºµÄ×°ÊÎÆ÷¡£¿ÉÒÔ¿´¿´ rultor.com ÉÏ RsPage ÈçºÎ×Ô¶¨Òå×°ÊÎÆ÷¡£
ÈçºÎʹÓÃÄ£°å£¿
ÈçÄãËù¼û£¬·µ»Ø¼òµ¥µÄ¡°Hello, world¡±Ò³Ãæ²¢²»ÊÇÒ»¸ö´óÎÊÌâ¡£µ«ÊÇ·µ»Ø¸ü¸´ÔÓµÄÊä³öÀýÈçHTMLÒ³Ãæ¡¢XMLÎĵµ¡¢JSONÊý¾Ý¼¯£¬ÓÖ¸ÃÔõô°ì£¿ÈÃÎÒÃÇ´ÓÒ»¸ö¼òµ¥µÄÄ£°åÒýÇæ¡°Velocity¡±¿ªÊ¼¡£ºÃ°É£¬ÆäʵËü²¢²»¼òµ¥¡£ËüÏ൱ǿ´ó£¬µ«ÊÇÎÒÖ»½¨ÒéÔÚ¼òµ¥ÇéÐÎÏÂʹÓá£ÏÂÃæÊǹØÓÚËüÈçºÎ¹¤×÷£º
final class TkIndex implements Take { @Override public Response act() { return new RsVelocity("Hello, ${name}") .with("name", "Jeffrey"); } } |
RsVelocity ¹¹ÔìÆ÷½ÓÊÜVelocityÄ£°å×÷ΪΨһ²ÎÊý¡£È»ºó£¬Äã¿ÉÒÔµ÷Óá°with()¡±·½·¨£¬ÍùVelocityÉÏÏÂÎÄ×¢ÈëÊý¾Ý¡£µ±µ½äÖȾHTTPÏìÓ¦µÄʱºò£¬RsVelocity
½«»á½«Ä£°åºÍÅäÖõÄÉÏÏÂÎĽøÐС°ÆÀ¹À¡±¡£ÔÙ´ÎÇ¿µ÷£¬ÎÒÖ»ÍÆ¼öÔڷdz£¼òµ¥µÄÊä³öʱʹÓÃÕâÖÖÄ£°å·½Ê½¡£
¶ÔÓÚ¸ü¸´ÔÓµÄHTMLÎĵµ£¬ÎÒ½«ÍƼöÄãʹÓýáºÏXemblyʹÓÃXML/XSLT¡£ÔÚÏÈǰµÄ¼¸Æª²©¿ÍÖÐÎÒ½âÊÍÁËÕâÖÖÏë·¨£¬XML+XSLT
in a Browser ºÍRESTful API and a Web Site in the Same
URL¡£ÕâÖÖ·½Ê½¼òµ¥Ç¿´ó¡ª¡ªÓÃJavaÉú³ÉXML£¬XSLT ´¦ÀíÆ÷½«Æäת»»³ÉHTMLÎĵµ¡£Õâ¾ÍÊÇÎÒÃÇÈçºÎ·ÖÀë±íʾºÍÊý¾Ý¡£ÔÚMVCÀ´¿´£¬XSLÑùʽ±íÊÇÒ»¸ö¡°ÊÓͼ¡±£¬TkIndex
ÊÇÒ»¸ö¡°¿ØÖÆÆ÷¡±¡£
²»¾ÃÎһᵥ¶ÀдһƪÎÄÕÂÀ´½éÉÜʹÓÃXemblyºÍXSLÄ£°åÉú³ÉÒ³Ãæ¡£
ͬʱ£¬ÎÒ»áÔÚTakes¿ò¼ÜÖÐΪ JSF/Facelets ºÍ JSP äÖȾ´´½¨×°ÊÎÆ÷¡£Èç¹ûÄã¶ÔÕⲿ·Ö¹¤×÷¸ÐÐËȤ£¬ÇëforkÕâ¸ö¿ò¼Ü²¢Ìá½»ÄãµÄpullÇëÇó¡£
ÈçºÎ³Ö¾Ã»¯£¿
ÏÖÔÚ£¬Ò»¸öÎÊÌâ¾Í³öÀ´ÁË¡£ÈçºÎ´¦ÀíÖîÈçÊý¾Ý¿â¡¢ÄÚ´æ½á¹¹¡¢ÍøÂçÁ¬½ÓÖ®ÀàµÄ³Ö¾Ã²ãʵÌ壿ÎҵĽ¨ÒéÊÇÔÚEntryÀàÖÐʵÀý»¯ËüÃÇ£¬²¢°ÑËüÃÇ×÷Ϊ²ÎÊý´«ÈëTsAppµÄ¹¹Ô캯ÊýÖС£È»ºó£¬TsApp½«»á°ÑËüÃÇ´«Èë×Ô¶¨ÒåµÄ¡°takes¡±µÄ¹¹Ô캯ÊýÖС£
ÀýÈ磬ÎÒÃÇÓÐÒ»¸öPostgreSQLÊý¾Ý¿â£¬°üº¬Ò»Ð©ÓÃÀ´äÖȾµÄ±íÊý¾Ý¡£ÕâÀïÎÒ½«ÔÚEntryÀàÖÐʵÀý»¯Êý¾Ý¿âÁ¬½Ó£¨Ê¹ÓÃ
BoneCPÁ¬½Ó³Ø£©£º
public final class Entry { public static void main(final String... args) throws Exception { new FtCLI(new TsApp(Entry.postgres()), args).start(Exit.NEVER); } private static Source postgres() { final BoneCPDataSource src = new BoneCPDataSource(); src.setDriverClass("org.postgresql.Driver"); src.setJdbcUrl("jdbc:postgresql://localhost/db"); src.setUser("root"); src.setPassword("super-secret-password"); return src; } } |
ÏÖÔÚ£¬TsAppµÄ¹¹ÔìÆ÷±ØÐë½ÓÊÜÒ»¸ö¡°java.sql.Source¡±ÀàÐ͵IJÎÊý£º
final class TsApp extends TsWrap { TsApp(final Source source) { super(TsApp.make(source)); } private static Takes make(final Source source) { return new TsFork( new FkRegex("/", new TkIndex(source)) ); } } |
TkIndex ÀàͬÑù½ÓÊÜÒ»¸öSourceÀàÐ͵IJÎÊý¡£ÎªÁËÈ¡SQL±íÊý¾Ý²¢°ÑËüת»»³ÉHTML£¬ÏàÐÅÄãÖªµÀTkIndexÄÚ²¿ÈçºÎ´¦ÀíµÄ¡£ÕâÀïµÄ¹Ø¼üµãÊÇÔÚÓ¦Óã¨TsAppÀàµÄʵÀý£©³õʼ»¯Ê±±ØÐë×¢ÈëÒÀÀµ¡£ÕâÊÇ´¿´â¸É¾»µÄÒÀÀµ×¢Èë»úÖÆ£¬ÍêÈ«ÎÞÐèÈκÎÈÝÆ÷¡£¸ü¶àÏà¹ØÔĶÁÇë²ÎÔÄ¡°Dependency
Injection Containers Are Code Polluters¡±¡£
µ¥Ôª²âÊÔ
ÒòΪÿ¸öÀàÊDz»¿É±äµÄ²¢ÇÒËùÓеÄÒÀÀµ¶¼ÊÇͨ¹ý¹¹Ô캯Êý×¢È룬ËùÒÔµ¥Ôª²âÊԷdz£¼òµ¥¡£±ÈÈçÎÒÃÇÏë²âÊÔ¡°TkStatus¡±£¬¼Ù¶¨Ëü½«»á·µ»ØÒ»¸öHTMLÏìÓ¦£¨ÎÒʹÓÃJUnit
4 ºÍHamcrest£©£º
import org.junit.Test; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; public final class TkIndexTest { @Test public void returnsHtmlPage() throws Exception { MatcherAssert.assertThat( new RsPrint( new TkStatus().act() ).printBody(), Matchers.equalsTo("<html>Hello, world!</html>") ); } } |
ͬÑù£¬ÎÒÃÇ¿ÉÒÔÔÚÒ»¸ö²âÊÔHTTP·þÎñÆ÷ÖÐÆô¶¯Õû¸öÓ¦ÓûòÕßÈκÎÒ»¸öµ¥¶ÀµÄ¡°take¡±£¬È»ºóͨ¹ýÕæÊµµÄTCPÌ×½Ó×Ö²âÊÔËüµÄÐÐΪ£»ÀýÈ磨ÎÒʹÓÃjcabi-http¹¹ÔìHTTPÇëÇó²¢ÇÒ¼ì²âÊä³ö£©£º
public final class TkIndexTest { @Test public void returnsHtmlPage() throws Exception { new FtRemote(new TsFixed(new TkIndex())).exec( new FtRemote.Script() { @Override public void exec(final URI home) throws IOException { new JdkRequest(home) .fetch() .as(RestResponse.class) .assertStatus(HttpURLConnection.HTTP_OK) .assertBody(Matchers.containsString("Hello, world!")); } } ); } } |
FtRemoteÔÚÈÎÒâµÄTCP¶Ë¿ÚÆô¶¯Ò»¸ö²âÊÔWeb·þÎñÆ÷£¬²¢ÇÒÔÚ FtRemote.Script
ÌṩµÄʵÀýÖе÷Óà exec() ·½·¨¡£´Ë·½·¨µÄµÚÒ»¸ö²ÎÊýÊÇ¸Õ²ÅÆô¶¯µÄweb·þÎñÆ÷Ö÷Ò³ÃæµÄURI¡£
Takes¿ò¼ÜµÄ¼Ü¹¹·Ç³£Ä£¿é»¯ÇÒÒ×ÓÚ×éºÏ¡£ÈκζÀÁ¢µÄ¡°take¡±¶¼¿ÉÒÔ×÷Ϊһ¸öµ¥¶ÀµÄ×é¼þ±»²âÊÔ£¬¾ø¶Ô¶ÀÁ¢ÓÚ¿ò¼ÜºÍÆäËü¡°takes¡±¡£
Ϊʲô½ÐÕâ¸öÃû×Ö£¿
ÕâÊÇÎÒÌýµ½×îÆµ·±µÄÎÊÌâ¡£Ïë·¨ºÜ¼òµ¥£¬ËüºÍµçÓ°Óйء£µ±ÖÆ×÷Ò»²¿µçӰʱ£¬¹¤×÷ÈËԱΪÁ˲¶×½ÏÖʵ»áÅÄÉãºÜ¶à¾µÍ·È»ºó·ÅÈëµçÓ°ÖС£Ã¿Ò»¸öÅÄÉã³Æ×÷Ò»¸ö¾µÍ·£¨take£©¡£
»»¾ä»°Ëµ£¬Ò»¸ö¾µÍ·¾ÍÏñÏÖʵµÄÒ»¸ö¿ìÕÕ¡£Ã¿Ò»¸ö¾µÍ·ÊµÀý´ú±íÌØ¶¨Ê±¿ÌµÄÒ»¸öÊÂʵ¡£Õâ¸öÊÂʵȻºóÒÔÏìÓ¦µÄÐÎʽ·¢Ë͸øÓû§¡£
ͬÑùµÄµÀÀíÒ²ÊÊÓÃÓÚ¿ò¼Ü¡£Ã¿¸öTakeʵÀý¶¼´ú±í×ÅÌØ¶¨Ä³¸öʱ¿ÌµÄÕæÊµ´æÔÚ¡£Õâ¸öÐÅÏ¢»áÒÔResponseÐÎʽ·¢ËÍ¡£
|