forked from playframework/play1
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
dc3346d
commit 0f513ea
Showing
30 changed files
with
705 additions
and
135 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
h1. Asynchronous programming with HTTP | ||
|
||
This section explains how to deal with asynchonism in a Play application to achieve typical long-polling, streaming and other comet-style applications that can scale to thousand of concurrent connections. | ||
|
||
h2. Suspending HTTP requests | ||
|
||
Play is intended to work with very short requests. It uses a fixed thread pool to process requests queued by the HTTP connector. To get optimum results, the thread pool should be as small as possible. We typically use the optimum value of **nb of processors + 1** to set the default pool size. | ||
|
||
That means that if a request is very long (for example waiting for a long computation) it will block the thread pool and penalize your application responsiveness. Of course you could add more threads to the pool, but it will result in wasted resources, and anyway the pool size will never be infinite. | ||
|
||
Think for example about a chat application where browsers send a blocking HTTP request that waits for a new message to display. These requests can be very very long (typically several seconds) and will block the thread pool. If you plan to allow 100 users to connect simultaneously to your chat application you will need to provision at least 100 threads. OK it’s feasible. But what about 1,000 users? Or 10,000? | ||
|
||
To resolve these use cases, Play allows you to temporarily suspend a request. The HTTP request will stay connected, but the request execution will be popped out of the thread pool and tried again later. You can either tell Play to try the request later after a fixed delay, or wait for a @Promise@ value to be available. | ||
|
||
p(note). **Tip**. You can see a real example in samples-and-tests/chat. | ||
|
||
For example, this action will launch a very long job and wait for its completion before returning the result to the HTTP response: | ||
|
||
bc. public static void generatePDF(Long reportId) { | ||
Promise<InputStream> pdf = new ReportAsPDFJob(report).now(); | ||
InputStream pdfStream = await(pdf); | ||
renderBinary(pdfStream); | ||
} | ||
|
||
Here we use @await(…)@ to ask Play to suspend the request until the @Promise<InputStream>@ value is redeem. | ||
|
||
h3. Continuations | ||
|
||
Because the framework needs to recover the thread you were using in order to use it to serve other requests, it has to suspend your code. In the previous Play version the await(…) equivalent was waitFor(…). And waitFor was suspending your action, and then recalling it later from the beginning. | ||
|
||
To make it easier to deal with asynchronous code we have introduced continuations. Continuations allow your code to be suspended and resumed transparently. So you write your code in a very imperative way, as: | ||
|
||
bc. public static void computeSomething() { | ||
Promise<String> delayedResult = veryLongComputation(…); | ||
String result = await(delayedResult); | ||
render(result); | ||
} | ||
|
||
In fact here, your code will be executed in 2 steps, in 2 different threads. But as you see it, it's very transparent for your application code. | ||
|
||
Using @await(…)@ and continuations, you could write a loop: | ||
|
||
bc. public static void loopWithoutBlocking() { | ||
for(int i=0; i<=10; i++) { | ||
Logger.info(i); | ||
await("1s"); | ||
} | ||
renderText("Loop finished"); | ||
} | ||
|
||
And using only 1 thread (which is the default in development mode) to process requests, Play is able to run concurrently these loops for several requests at the same time. | ||
|
||
h2. HTTP response streaming | ||
|
||
Now, because you are able to loop without blocking the request, perhaps you want to send data to the browser as soon you have part of the result available. It is the point of the "Content-Type:Chunked" HTTP response type. It allows to send your HTTP response is several times using multiples chunks. The browser will receive these chunk as soon as they are published. | ||
|
||
Using @await(…)@ and continuations, you can now achieve that using: | ||
|
||
bc. public static void generateLargeCSV() { | ||
CSVGenerator generator = new CSVGenerator(); | ||
response.contentType = "text/csv"; | ||
while(generator.hasMoreData()) { | ||
String someCsvData = await(generator.nextDataChunk()); | ||
response.writeChunk(someCsvData); | ||
} | ||
} | ||
|
||
Even if the CSV generation take 1 hour, Play is able to process simultaneously several requests using a single thread, sending back the generated data to the client as soon as they are available. | ||
|
||
h2. Using WebSockets | ||
|
||
WebSockets are a way to open a 2 way communication channel between a browser and your application. On the browser side, you open a socket using a "ws://" url: | ||
|
||
bc. new Socket("ws://localhost:9000/helloSocket?name=Guillaume") | ||
|
||
On the Play side you declare a WS route: | ||
|
||
bc. WS /helloSocket MyWebSocket.hello | ||
|
||
MyWebSocket is a WebSocketController. A WebSocket controller is like a standard HTTP controller but handle different concepts: | ||
|
||
* It has a request object, but no response object. | ||
* It has access to the session but for read only. | ||
* It doesn't have renderArgs, routeArgs, nor flash scope. | ||
* It can read params only from the route pattern or from the QueryString. | ||
* It has 2 communication channels: inbound and outbound. | ||
|
||
When the client connects to the **ws://localhost:9000/helloSocket** socket, Play will invoke the @MyWebSocket.hello@ action method. Once the @MyWebSocket.hello@ action method exit, the socket is closed. | ||
|
||
So a very basic socket example would be; | ||
|
||
bc. public class MyWebSocket extends WebSocketController { | ||
|
||
public static void hello(String name) { | ||
outbound.send("Hello %s!", name); | ||
} | ||
|
||
} | ||
|
||
Here when the client connects to the socket, it receive the 'Hello Guillaume' message, and then Play closes the socket. | ||
|
||
Of course usually you don't want to close the socket immediately. But it is easy to achieve using @await(…)@ and continuations. | ||
|
||
For example a basic Echo server: | ||
|
||
bc. public class MyWebSocket extends WebSocketController { | ||
|
||
public static void echo() { | ||
while(inbound.isOpen()) { | ||
WebSocketEvent e = await(inbound.nextEvent()); | ||
if(e instanceof WebSocketFrame) { | ||
WebSocketFrame frame = (WebSocketFrame)e; | ||
if(!e.isBinary) { | ||
if(frame.textData.equals("quit")) { | ||
outbound.send("Bye!"); | ||
disconnect(); | ||
} else { | ||
outbound.send("Echo: %s", frame.textData); | ||
} | ||
} | ||
} | ||
if(e instanceof WebSocketClose) { | ||
Logger.info("Socket closed!"); | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
In the previous example, the imbricated 'if' and 'cast' soup was tedious to write and error prone. And here Java sucks. Even for this simple case it is not easy to handle. And for more complicated cases where you combine several streams, and have even more event types, it becomes a nightmare. | ||
|
||
That's why we have introduced a kind of basic pattern matching in Java in the "play.libs.F":libs#FunctionalprogrammingwithJava library. | ||
|
||
So we can rewrite our previous echo sample as: | ||
|
||
bc. public static void echo() { | ||
while(inbound.isOpen()) { | ||
WebSocketEvent e = await(inbound.nextEvent()); | ||
|
||
for(String quit: TextFrame.and(Equals("quit")).match(e)) { | ||
outbound.send("Bye!"); | ||
disconnect(); | ||
} | ||
|
||
for(String msg: TextFrame.match(e)) { | ||
outbound.send("Echo: %s", frame.textData); | ||
} | ||
|
||
for(WebSocketClose closed: SocketClosed.match(e)) { | ||
Logger.info("Socket closed!"); | ||
} | ||
} | ||
} | ||
|
||
p(note). **Continuing the discussion** | ||
|
||
Next, doing %(next)"Ajax request":ajax%. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,12 +18,12 @@ email.addTo("[email protected]"); | |
email.setFrom([email protected]", "Nicolas"); | ||
email.setSubject("Test email with inline image"); | ||
// embed the image and get the content id | ||
URL url = new URL("http://www.zenexity.fr/wp-content/themes/zenexity/images/logo.png"); | ||
URL url = new URL("http://www.zenexity.fr/wp-content/themes/images/logo.png"); | ||
String cid = email.embed(url, "Zenexity logo"); | ||
// set the html message | ||
email.setHtmlMsg("<html>Zenexity logo - <img src=\"cid:"+cid+"\"></html>"); | ||
// set the alternative message | ||
email.setTextMsg("Your email client does not support HTML messages, too bad :("); | ||
email.setTextMsg("Your email client does not support HTML, too bad :("); | ||
|
||
For more information see the "Commons Email documentation":http://commons.apache.org/email/userguide.html. | ||
|
||
|
@@ -76,8 +76,11 @@ The template for the lostPassword method could look like this: | |
|
||
**app/views/Mails/lostPassword.html** | ||
|
||
bc. <html><body><head>...</head><body><img src="mycompany.com/images"/><p>Hello ${user.name},<br/> | ||
Your new password is <b>${newpassword}</b>. | ||
bc. <html> | ||
<body><head>...</head><body> | ||
<img src="mycompany.com/images"/> | ||
<p> | ||
Hello ${user.name}, Your new password is <b>${newpassword}</b>. | ||
</p> | ||
</body> | ||
</html> | ||
|
@@ -95,7 +98,7 @@ The template for the lostPassword method could look like this: | |
|
||
**app/views/Mails/lostPassword.txt** | ||
|
||
bc.Hello ${user.name}, | ||
bc. Hello ${user.name}, | ||
|
||
Your new password is ${newpassword}. | ||
|
||
|
@@ -107,15 +110,15 @@ h3. Links to your application in e-mail | |
|
||
Your can include links to your application in e-mails like this: | ||
|
||
bc.@@{application.index} | ||
bc. @@{application.index} | ||
|
||
If you send mails from Jobs you have to configure **application.baseUrl** in **application.conf**. | ||
**application.baseUrl** must be a valid external baseurl to your application. | ||
|
||
If the website playframework.org where to send you an e-mail from inside a Job, its configuration | ||
would look like this: | ||
|
||
bc.application.baseUrl=http://www.playframework.org/ | ||
bc. application.baseUrl=http://www.playframework.org/ | ||
|
||
h2. <a name="smtp">SMTP configuration</a> | ||
|
||
|
@@ -161,7 +164,6 @@ mail.smtp.user=yourGmailLogin | |
mail.smtp.pass=yourGmailPassword | ||
mail.smtp.channel=ssl | ||
|
||
|
||
p(note). **Continuing the discussion** | ||
|
||
Next: %(next)"Security guide":security%. | ||
Now we shall move on to %(next)"Testing the application":test%. |
Oops, something went wrong.