├── .gitignore
├── LICENSE
├── README.md
├── out
└── production
│ ├── spring-mvc-asynch-teststub
│ └── application.properties
│ └── spring-mvc-asynch
│ ├── application.properties
│ └── logback.xml
├── spring-mvc-asynch-teststub
├── build.gradle
├── docs
│ ├── blocking
│ │ ├── Gatling Stats - 1. Global Information.pdf
│ │ ├── Gatling Stats - 2. Details.pdf
│ │ └── JConsole 2014-04-01 at 10.04.27.png
│ └── non-blocking
│ │ ├── Gatling Stats - 1. Global Information.pdf
│ │ ├── Gatling Stats - 2. Detailst.pdf
│ │ └── JConsole 2014-03-31 at 14.58.25.png
├── gatling
│ └── spring-mvc-asynch-teststub-simulation.scala
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
│ ├── main
│ ├── java
│ │ └── se
│ │ │ └── callista
│ │ │ └── springmvc
│ │ │ └── asynch
│ │ │ └── teststub
│ │ │ ├── Application.java
│ │ │ ├── MyEmbeddedServletContainerCustomizer.java
│ │ │ ├── ProcessingController.java
│ │ │ ├── ProcessingStatus.java
│ │ │ └── ProcessingTask.java
│ └── resources
│ │ ├── application.properties
│ │ └── logback.xml
│ └── test
│ └── java
│ └── se
│ └── callista
│ └── springmvc
│ └── asynch
│ └── teststub
│ └── ProcessingControllerTest.java
└── spring-mvc-asynch
├── .gitignore
├── build.gradle
├── gatling
└── spring-mvc-asynch-simulation.scala
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── src
├── main
│ ├── java
│ │ └── se
│ │ │ └── callista
│ │ │ └── springmvc
│ │ │ └── asynch
│ │ │ ├── Application.java
│ │ │ ├── common
│ │ │ ├── deferredresult
│ │ │ │ ├── DeferredResultStateMachineCallback.java
│ │ │ │ └── DeferredResultWithBlockingWait.java
│ │ │ ├── lambdasupport
│ │ │ │ ├── AsyncHttpClientLambdaAware.java
│ │ │ │ ├── Completed.java
│ │ │ │ └── Error.java
│ │ │ ├── log
│ │ │ │ ├── LogHelper.java
│ │ │ │ └── LogHelperFactory.java
│ │ │ └── statemachine
│ │ │ │ ├── Processor.java
│ │ │ │ ├── State.java
│ │ │ │ ├── StateMachine.java
│ │ │ │ ├── StateMachineCallback.java
│ │ │ │ └── internal
│ │ │ │ └── StateProcessingStepIterator.java
│ │ │ ├── config
│ │ │ └── MyEmbeddedServletContainerCustomizer.java
│ │ │ └── pattern
│ │ │ ├── aggregator
│ │ │ ├── blocking
│ │ │ │ ├── AggregatorBlockingController.java
│ │ │ │ └── DbLookup.java
│ │ │ └── nonblocking
│ │ │ │ ├── callback
│ │ │ │ ├── AggregatorCallback.java
│ │ │ │ ├── AggregatorEventHandler.java
│ │ │ │ ├── AggregatorNonBlockingCallbackController.java
│ │ │ │ └── DbLookupRunnable.java
│ │ │ │ └── lambda
│ │ │ │ ├── AggregatorNonBlockingLambdaController.java
│ │ │ │ ├── DbLookup.java
│ │ │ │ └── Executor.java
│ │ │ ├── router
│ │ │ ├── blocking
│ │ │ │ └── RouterBlockingController.java
│ │ │ └── nonblocking
│ │ │ │ ├── anonymous
│ │ │ │ └── RouterNonBlockingAnonymousController.java
│ │ │ │ ├── callback
│ │ │ │ ├── RouterCallback.java
│ │ │ │ └── RouterNonBlockingCallbackController.java
│ │ │ │ ├── lambda
│ │ │ │ └── RouterNonBlockingLambdaController.java
│ │ │ │ └── spring
│ │ │ │ ├── RouterCallback_Spring_AsyncRestTemplate.java
│ │ │ │ └── RouterNonBlockingSpringController.java
│ │ │ └── routingslip
│ │ │ ├── blocking
│ │ │ ├── plain
│ │ │ │ └── RoutingSlipBlockingPlainController.java
│ │ │ └── statemachine
│ │ │ │ ├── RoutingSlipBlockingConfiguration.java
│ │ │ │ ├── RoutingSlipBlockingStateMachineController.java
│ │ │ │ └── SynchProcessor.java
│ │ │ └── nonblocking
│ │ │ ├── lambda
│ │ │ └── RoutingSlipNonBlockingLambdaController.java
│ │ │ └── statemachine
│ │ │ ├── AsynchProcessor.java
│ │ │ ├── AsynchProcessorCallback.java
│ │ │ ├── RoutingSlipNonBlockingConfiguration.java
│ │ │ └── RoutingSlipNonBlockingStateMachineController.java
│ └── resources
│ │ ├── application.properties
│ │ └── logback.xml
└── test
│ └── java
│ └── se
│ └── callista
│ └── springmvc
│ └── asynch
│ ├── common
│ ├── AsynchTestBase.java
│ ├── ProcessingStatus.java
│ └── embeddedhttpserver
│ │ ├── EmbeddedHttpServer.java
│ │ ├── EmbeddedHttpServerTestBase.java
│ │ └── RequestHandler.java
│ └── pattern
│ ├── aggregator
│ └── AggregatorControllerTest.java
│ ├── router
│ └── RouterControllerTest.java
│ └── routingslip
│ └── RoutingSlipControllerTest.java
└── todo.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | # Java Files #
2 | *.class
3 |
4 | # Package Files #
5 | # *.jar, we need to checkin gradle/wrapper/gradle-wrapper.jar
6 | *.war
7 | *.ear
8 |
9 | # Eclipse files #
10 | .settings
11 | .classpath
12 | .project
13 |
14 | # IntelliJ files #
15 | .idea
16 | local.properties
17 | *.iml
18 | out
19 |
20 | # Gradle files #
21 | .gradle
22 | build
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | blog-non-blocking-rest-service-with-spring-mvc
2 | ==============================================
3 |
4 | Source code for the blog: http://callistaenterprise.se/blogg/teknik/2014/04/22/c10k-developing-non-blocking-rest-services-with-spring-mvc/
5 |
--------------------------------------------------------------------------------
/out/production/spring-mvc-asynch-teststub/application.properties:
--------------------------------------------------------------------------------
1 | server.port = 9090
2 |
3 | #THE DEFAULT VALUE FOT THREAD POOL SIZE IN TOMCAT
4 | servlet.container.maxThreads=200
5 | #servlet.container.maxThreads=50 FOR NON-BLOCKING TESTS
6 | #servlet.container.maxThreads=500 FOR BLOCKING TESTS
7 |
8 | statistics.requestsPerLog=5000
9 | #statistics.requestsPerLog=1000 FOR BLOCKING TESTS
10 |
--------------------------------------------------------------------------------
/out/production/spring-mvc-asynch/application.properties:
--------------------------------------------------------------------------------
1 | server.port=9080
2 | servlet.container.maxThreads=50
3 |
4 | threadPool.db.init_size=5
5 | threadPool.db.max_size=50
6 | threadPool.db.queue_size=1000000
7 |
8 | #sp.blocking.url=http://localhost:9090/process-blocking
9 | #sp.non_blocking.url=http://localhost:${server.port}/process-non-blocking
10 | sp.server.port=9090
11 | sp.non_blocking.url=http://localhost:${sp.server.port}/process-non-blocking
12 |
13 | aggregator.timeoutMs=3000
14 |
15 | statistics.requestsPerLog=5000
16 |
--------------------------------------------------------------------------------
/out/production/spring-mvc-asynch/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | System.out
13 |
14 | %d{yyyy-MM-dd HH:mm:ss:SSS} %-5p %t %c{1}:%L - %m%n
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | mavenLocal()
5 | }
6 | dependencies {
7 | classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.1.RELEASE")
8 | }
9 | }
10 |
11 | apply plugin: 'java'
12 | apply plugin: 'eclipse'
13 | apply plugin: 'idea'
14 | apply plugin: 'spring-boot'
15 | apply plugin: 'war'
16 |
17 | war {
18 | baseName = 'spring-mvc-asynch-teststub'
19 | version = '1.0.0-SNAPSHOT'
20 | }
21 |
22 | repositories {
23 | mavenCentral()
24 | }
25 |
26 | dependencies {
27 | compile("org.springframework.boot:spring-boot-starter-web")
28 | // compile("org.springframework.boot:spring-boot-starter-jetty")
29 | compile("org.springframework.boot:spring-boot-starter-actuator")
30 | compile("javax.xml.bind:jaxb-api:2.1")
31 | compile("com.sun.xml.bind:jaxb-impl:2.1.9")
32 | testCompile("org.springframework.boot:spring-boot-starter-test")
33 | }
34 |
35 | task wrapper(type: Wrapper) {
36 | gradleVersion = '1.11'
37 | }
38 |
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/docs/blocking/Gatling Stats - 1. Global Information.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/callistaenterprise/blog-non-blocking-rest-service-with-spring-mvc/28491c3c4f50f08939d0ac3bc36e5668d441d563/spring-mvc-asynch-teststub/docs/blocking/Gatling Stats - 1. Global Information.pdf
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/docs/blocking/Gatling Stats - 2. Details.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/callistaenterprise/blog-non-blocking-rest-service-with-spring-mvc/28491c3c4f50f08939d0ac3bc36e5668d441d563/spring-mvc-asynch-teststub/docs/blocking/Gatling Stats - 2. Details.pdf
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/docs/blocking/JConsole 2014-04-01 at 10.04.27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/callistaenterprise/blog-non-blocking-rest-service-with-spring-mvc/28491c3c4f50f08939d0ac3bc36e5668d441d563/spring-mvc-asynch-teststub/docs/blocking/JConsole 2014-04-01 at 10.04.27.png
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/docs/non-blocking/Gatling Stats - 1. Global Information.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/callistaenterprise/blog-non-blocking-rest-service-with-spring-mvc/28491c3c4f50f08939d0ac3bc36e5668d441d563/spring-mvc-asynch-teststub/docs/non-blocking/Gatling Stats - 1. Global Information.pdf
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/docs/non-blocking/Gatling Stats - 2. Detailst.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/callistaenterprise/blog-non-blocking-rest-service-with-spring-mvc/28491c3c4f50f08939d0ac3bc36e5668d441d563/spring-mvc-asynch-teststub/docs/non-blocking/Gatling Stats - 2. Detailst.pdf
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/docs/non-blocking/JConsole 2014-03-31 at 14.58.25.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/callistaenterprise/blog-non-blocking-rest-service-with-spring-mvc/28491c3c4f50f08939d0ac3bc36e5668d441d563/spring-mvc-asynch-teststub/docs/non-blocking/JConsole 2014-03-31 at 14.58.25.png
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/gatling/spring-mvc-asynch-teststub-simulation.scala:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import com.excilys.ebi.gatling.core.Predef._
4 | import com.excilys.ebi.gatling.http.Predef._
5 | import com.excilys.ebi.gatling.jdbc.Predef._
6 | import com.excilys.ebi.gatling.http.Headers.Names._
7 | import akka.util.duration._
8 | import bootstrap._
9 |
10 | /**
11 | * Started with a command like:
12 | * $ cd $GATLING_HOME/bin
13 | * $ ./gatling.sh -s "basic.SpringMvcAsynchTeststubSimulation"
14 | */
15 | class SpringMvcAsynchTeststubSimulation extends Simulation {
16 |
17 | val rampUpTimeSecs = 60
18 | val testTimeSecs = 360
19 | val noOfUsers = 5000
20 | val minWaitMs = 1000 milliseconds
21 | val maxWaitMs = 3000 milliseconds
22 |
23 | val baseURL = "http://localhost:9090"
24 | val baseName = "spring-mvc-asynch-teststub"
25 | val requestName = baseName + "-request"
26 | val scenarioName = baseName + "-scenario"
27 | val URI = "/process-non-blocking?minMs=1000&maxMs=2000"
28 | // val URI = "/process-blocking?minMs=1000&maxMs=2000"
29 |
30 | val httpConf = httpConfig.baseURL(baseURL)
31 |
32 | val http_headers = Map(
33 | "Accept-Encoding" -> "gzip,deflate",
34 | "Content-Type" -> "text/json;charset=UTF-8",
35 | "Keep-Alive" -> "115")
36 |
37 | val scn = scenario(scenarioName)
38 | .during(testTimeSecs) {
39 | exec(
40 | http(requestName)
41 | .get(URI)
42 | .headers(http_headers)
43 | .check(status.is(200))
44 | )
45 | .pause(minWaitMs, maxWaitMs)
46 | }
47 | setUp(scn.users(noOfUsers).ramp(rampUpTimeSecs).protocolConfig(httpConf))
48 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/callistaenterprise/blog-non-blocking-rest-service-with-spring-mvc/28491c3c4f50f08939d0ac3bc36e5668d441d563/spring-mvc-asynch-teststub/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jul 18 06:19:10 CEST 2014
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip
7 |
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/src/main/java/se/callista/springmvc/asynch/teststub/Application.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.teststub;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
6 | import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
7 | import org.springframework.boot.SpringApplication;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.ComponentScan;
10 |
11 | @ComponentScan
12 | @EnableAutoConfiguration
13 | public class Application {
14 |
15 | @SuppressWarnings("unused")
16 | private static final Logger LOG = LoggerFactory.getLogger(Application.class);
17 |
18 | @Bean
19 | public EmbeddedServletContainerCustomizer embeddedServletCustomizer(){
20 | return new MyEmbeddedServletContainerCustomizer();
21 | }
22 |
23 | public static void main(String[] args) {
24 | SpringApplication.run(Application.class, args);
25 | }
26 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/src/main/java/se/callista/springmvc/asynch/teststub/MyEmbeddedServletContainerCustomizer.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.teststub;
2 |
3 | import org.apache.catalina.connector.Connector;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Value;
7 | import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
8 | import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
9 | import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
10 | import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
11 |
12 | public class MyEmbeddedServletContainerCustomizer implements EmbeddedServletContainerCustomizer {
13 |
14 | private static final Logger LOG = LoggerFactory.getLogger(MyEmbeddedServletContainerCustomizer.class);
15 |
16 | @Value("${servlet.container.maxThreads}")
17 | private int MAX_THREADS;
18 |
19 | @Override
20 | public void customize(ConfigurableEmbeddedServletContainer factory) {
21 | if(factory instanceof TomcatEmbeddedServletContainerFactory) {
22 | customizeTomcat((TomcatEmbeddedServletContainerFactory) factory);
23 | }
24 | }
25 |
26 | public void customizeTomcat(TomcatEmbeddedServletContainerFactory factory) {
27 | factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
28 | @Override
29 | public void customize(Connector connector) {
30 | Object defaultMaxThreads = connector.getAttribute("maxThreads");
31 | connector.setAttribute("maxThreads", MAX_THREADS);
32 | LOG.info("Changed Tomcat connector maxThreads from " + defaultMaxThreads + " to " + MAX_THREADS);
33 | }
34 | });
35 | }
36 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/src/main/java/se/callista/springmvc/asynch/teststub/ProcessingController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.teststub;
2 |
3 | import java.lang.management.ManagementFactory;
4 | import java.lang.management.OperatingSystemMXBean;
5 | import java.util.Timer;
6 | import java.util.concurrent.atomic.AtomicLong;
7 |
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.beans.factory.annotation.Value;
11 | import org.springframework.web.bind.annotation.RequestMapping;
12 | import org.springframework.web.bind.annotation.RequestParam;
13 | import org.springframework.web.bind.annotation.RestController;
14 | import org.springframework.web.context.request.async.DeferredResult;
15 |
16 | import com.sun.management.UnixOperatingSystemMXBean;
17 |
18 | @RestController
19 | public class ProcessingController {
20 |
21 | private static final Logger LOG = LoggerFactory.getLogger(ProcessingController.class);
22 |
23 | private static OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
24 | private static final AtomicLong lastRequestId = new AtomicLong(0);
25 | private static final AtomicLong concurrentRequests = new AtomicLong(0);
26 | private static long maxConcurrentRequests = 0;
27 |
28 | private Timer timer = new Timer();
29 |
30 | @Value("${statistics.requestsPerLog}")
31 | private int STAT_REQS_PER_LOG;
32 | private int defaultMinMs = 0;
33 | private int defaultMaxMs = 0;
34 |
35 | @RequestMapping("/set-default-processing-time")
36 | public void setDefaultProcessingTime(
37 | @RequestParam(value = "minMs", required = true) int minMs,
38 | @RequestParam(value = "maxMs", required = true) int maxMs) {
39 | this.defaultMinMs = minMs;
40 | this.defaultMaxMs = maxMs;
41 | LOG.info("Set default response time to {} - {} ms.", minMs, maxMs);
42 | }
43 |
44 | /**
45 | * Sample usage: curl "http://localhost:9090/process-blocking?minMs=1000&maxMs=2000"
46 | *
47 | * @param minMs
48 | * @param maxMs
49 | * @return
50 | */
51 | @RequestMapping("/process-blocking")
52 | public ProcessingStatus blockingProcessing(
53 | @RequestParam(value = "minMs", required = false, defaultValue = "0") int minMs,
54 | @RequestParam(value = "maxMs", required = false, defaultValue = "0") int maxMs) {
55 |
56 | long reqId = lastRequestId.getAndIncrement();
57 | long concReqs = concurrentRequests.getAndIncrement();
58 |
59 | updateStatistics(reqId, concReqs);
60 |
61 | int processingTimeMs = calculateProcessingTime(minMs, maxMs);
62 |
63 | LOG.debug("{}: Start blocking request #{}, processing time: {} ms.", concReqs, reqId, processingTimeMs);
64 |
65 | try {
66 | Thread.sleep(processingTimeMs);
67 | }
68 | catch (InterruptedException e) {}
69 |
70 | finally {
71 | concurrentRequests.decrementAndGet();
72 | LOG.debug("{}: Processing of blocking request #{} is done", concReqs, reqId);
73 | }
74 |
75 | return new ProcessingStatus("Ok", processingTimeMs);
76 | }
77 |
78 | /**
79 | * Sample usage: curl "http://localhost:9090/process-non-blocking?minMs=1000&maxMs=2000"
80 | *
81 | * @param minMs
82 | * @param maxMs
83 | * @return
84 | */
85 | @RequestMapping("/process-non-blocking")
86 | public DeferredResult nonBlockingProcessing(
87 | @RequestParam(value = "minMs", required = false, defaultValue = "0") int minMs,
88 | @RequestParam(value = "maxMs", required = false, defaultValue = "0") int maxMs) {
89 |
90 | long reqId = lastRequestId.getAndIncrement();
91 | long concReqs = concurrentRequests.getAndIncrement();
92 |
93 | updateStatistics(reqId, concReqs);
94 |
95 | int processingTimeMs = calculateProcessingTime(minMs, maxMs);
96 |
97 | LOG.debug("{}: Start non-blocking request #{}, processing time: {} ms.", concReqs, reqId, processingTimeMs);
98 |
99 | // Create the deferredResult and initiate a callback object, task, with it
100 | DeferredResult deferredResult = new DeferredResult<>();
101 | ProcessingTask task = new ProcessingTask(reqId, concurrentRequests, processingTimeMs, deferredResult);
102 |
103 | // Schedule the task for asynch completion in the future
104 | timer.schedule(task, processingTimeMs);
105 |
106 | LOG.debug("{}: Processing of non-blocking request #{} leave the request thread", concReqs, reqId);
107 |
108 | // Return to let go of the precious thread we are holding on to...
109 | return deferredResult;
110 | }
111 |
112 | private void updateStatistics(long reqId, long concReqs) {
113 | if (concReqs > maxConcurrentRequests) {
114 | maxConcurrentRequests = concReqs;
115 | }
116 |
117 | if (reqId % STAT_REQS_PER_LOG == 0 && reqId > 0) {
118 | Object openFiles = "UNKNOWN";
119 | if (os instanceof UnixOperatingSystemMXBean) {
120 | openFiles = ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount();
121 | }
122 | LOG.info("Statistics: noOfReqs: {}, maxConcReqs: {}, openFiles: {}", reqId, maxConcurrentRequests, openFiles);
123 | }
124 | }
125 |
126 | private int calculateProcessingTime(int minMs, int maxMs) {
127 | if (minMs == 0 && maxMs == 0) {
128 | minMs = defaultMinMs;
129 | maxMs = defaultMaxMs;
130 | }
131 |
132 | if (maxMs < minMs) maxMs = minMs;
133 | int processingTimeMs = minMs + (int) (Math.random() * (maxMs - minMs));
134 | return processingTimeMs;
135 | }
136 |
137 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/src/main/java/se/callista/springmvc/asynch/teststub/ProcessingStatus.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.teststub;
2 |
3 | import javax.xml.bind.annotation.XmlElement;
4 | import javax.xml.bind.annotation.XmlRootElement;
5 |
6 | @XmlRootElement
7 | public class ProcessingStatus {
8 |
9 | @XmlElement
10 | private final String status;
11 |
12 | @XmlElement
13 | private final int processingTimeMs;
14 |
15 | public ProcessingStatus() {
16 | status = "UNKNOWN";
17 | processingTimeMs = -1;
18 | }
19 |
20 | public ProcessingStatus(String status, int processingTimeMs) {
21 | this.status = status;
22 | this.processingTimeMs = processingTimeMs;
23 | }
24 |
25 | public String getStatus() {
26 | return status;
27 | }
28 |
29 | public int getProcessingTimeMs() {
30 | return processingTimeMs;
31 | }
32 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/src/main/java/se/callista/springmvc/asynch/teststub/ProcessingTask.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.teststub;
2 |
3 | import java.util.TimerTask;
4 | import java.util.concurrent.atomic.AtomicLong;
5 |
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springframework.web.context.request.async.DeferredResult;
9 |
10 | public class ProcessingTask extends TimerTask {
11 |
12 | private static final Logger LOG = LoggerFactory.getLogger(ProcessingTask.class);
13 |
14 | private long reqId;
15 | private AtomicLong concurrentRequests;
16 | private DeferredResult deferredResult;
17 | private int processingTimeMs;
18 |
19 | public ProcessingTask(long reqId, AtomicLong concurrentRequests, int processingTimeMs, DeferredResult deferredResult) {
20 | this.reqId = reqId;
21 | this.concurrentRequests = concurrentRequests;
22 | this.processingTimeMs = processingTimeMs;
23 | this.deferredResult = deferredResult;
24 | }
25 |
26 | @Override
27 | public void run() {
28 |
29 | long concReqs = concurrentRequests.getAndDecrement();
30 |
31 | if (deferredResult.isSetOrExpired()) {
32 | LOG.warn("{}: Processing of non-blocking request #{} already expired", concReqs, reqId);
33 | } else {
34 | boolean deferredStatus = deferredResult.setResult(new ProcessingStatus("Ok", processingTimeMs));
35 | LOG.debug("{}: Processing of non-blocking request #{} done, deferredStatus = {}", concReqs, reqId, deferredStatus);
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port = 9090
2 |
3 | #THE DEFAULT VALUE FOT THREAD POOL SIZE IN TOMCAT
4 | servlet.container.maxThreads=200
5 | #servlet.container.maxThreads=50 FOR NON-BLOCKING TESTS
6 | #servlet.container.maxThreads=500 FOR BLOCKING TESTS
7 |
8 | statistics.requestsPerLog=5000
9 | #statistics.requestsPerLog=1000 FOR BLOCKING TESTS
10 |
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | System.out
13 |
14 | %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %t %c{1}:%L - %m%n
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/spring-mvc-asynch-teststub/src/test/java/se/callista/springmvc/asynch/teststub/ProcessingControllerTest.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.teststub;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.mockito.MockitoAnnotations;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.SpringApplicationConfiguration;
11 | import org.springframework.http.MediaType;
12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
13 | import org.springframework.test.context.web.WebAppConfiguration;
14 | import org.springframework.test.web.servlet.MockMvc;
15 | import org.springframework.test.web.servlet.MvcResult;
16 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
17 | import org.springframework.web.context.WebApplicationContext;
18 |
19 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
22 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
24 |
25 | /**
26 | * Created by magnus on 07/06/14.
27 | */
28 | @RunWith(SpringJUnit4ClassRunner.class)
29 | @SpringApplicationConfiguration(classes = Application.class)
30 | @WebAppConfiguration
31 | public class ProcessingControllerTest {
32 |
33 | private MockMvc mockMvc;
34 |
35 | @Autowired
36 | WebApplicationContext wac;
37 |
38 | private final String expectedResult = "{\"status\":\"Ok\",\"processingTimeMs\":2000}";
39 |
40 | @Before
41 | public void setup(){
42 |
43 | // Process mock annotations
44 | MockitoAnnotations.initMocks(this);
45 |
46 | // Setup Spring test in webapp-mode (same config as spring-boot)
47 | this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
48 | }
49 |
50 | @Test
51 | public void testProcessBlocking() throws Exception{
52 | this.mockMvc.perform(get("/process-blocking?minMs=2000&maxMs=2000"))
53 | .andExpect(status().isOk())
54 | .andExpect(content().contentType("application/json;charset=UTF-8"))
55 | .andExpect(content().string(expectedResult));
56 | }
57 |
58 | @Test
59 | public void testProcessNonBlocking() throws Exception {
60 |
61 | MvcResult mvcResult = this.mockMvc.perform(get("/process-non-blocking?minMs=2000&maxMs=2000"))
62 | .andExpect(request().asyncStarted())
63 | .andReturn();
64 |
65 | mvcResult.getAsyncResult();
66 |
67 | this.mockMvc.perform(asyncDispatch(mvcResult))
68 | .andExpect(status().isOk())
69 | .andExpect(content().contentType("application/json;charset=UTF-8"))
70 | .andExpect(content().string(expectedResult));
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /bin
3 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | mavenLocal()
5 | }
6 | dependencies {
7 | classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.1.RELEASE")
8 | }
9 | }
10 |
11 | apply plugin: 'java'
12 | apply plugin: 'eclipse'
13 | apply plugin: 'idea'
14 | apply plugin: 'spring-boot'
15 | apply plugin: 'war'
16 |
17 | sourceCompatibility = 1.8
18 |
19 | war {
20 | baseName = 'spring-mvc-asynch'
21 | version = '1.0.0-SNAPSHOT'
22 | }
23 |
24 | repositories {
25 | mavenCentral()
26 | }
27 |
28 | dependencies {
29 | compile("org.springframework.boot:spring-boot-starter-web")
30 | compile("org.springframework.boot:spring-boot-starter-jetty")
31 | compile("org.springframework.boot:spring-boot-starter-actuator")
32 | compile("com.ning:async-http-client:1.8.4")
33 | compile("javax.xml.bind:jaxb-api:2.1")
34 | compile("com.sun.xml.bind:jaxb-impl:2.1.9")
35 | testCompile("org.springframework.boot:spring-boot-starter-test")
36 | // testCompile("com.github.tomakehurst:wiremock:1.46")
37 | testCompile("org.apache.commons:commons-io:1.3.2")
38 | testCompile("org.apache.httpcomponents:httpclient:4.3.4")
39 | }
40 |
41 | task wrapper(type: Wrapper) {
42 | gradleVersion = '1.11'
43 | }
44 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/gatling/spring-mvc-asynch-simulation.scala:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import com.excilys.ebi.gatling.core.Predef._
4 | import com.excilys.ebi.gatling.http.Predef._
5 | import com.excilys.ebi.gatling.jdbc.Predef._
6 | import com.excilys.ebi.gatling.http.Headers.Names._
7 | import akka.util.duration._
8 | import bootstrap._
9 |
10 | class SpringMvcAsynchSimulation extends Simulation {
11 |
12 | val rampUpTimeSecs = 60
13 | val testTimeSecs = 360
14 | val noOfUsers = 5000
15 | val minWaitMs = 1000 milliseconds
16 | val maxWaitMs = 3000 milliseconds
17 |
18 | val baseURL = "http://localhost:9080"
19 | val baseName = "spring-mvc-asynch"
20 | val requestName = baseName + "-request"
21 | val scenarioName = baseName + "-scenario"
22 | val URI = "/route-non-blocking?minMs=1000&maxMs=2000"
23 |
24 | val httpConf = httpConfig.baseURL(baseURL)
25 |
26 | val http_headers = Map(
27 | "Accept-Encoding" -> "gzip,deflate",
28 | "Content-Type" -> "text/json;charset=UTF-8",
29 | "Keep-Alive" -> "115")
30 |
31 | val scn = scenario(scenarioName)
32 | .during(testTimeSecs) {
33 | exec(
34 | http(requestName)
35 | .get(URI)
36 | .headers(http_headers)
37 | .check(status.is(200))
38 | )
39 | .pause(minWaitMs, maxWaitMs)
40 | }
41 | setUp(scn.users(noOfUsers).ramp(rampUpTimeSecs).protocolConfig(httpConf))
42 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/callistaenterprise/blog-non-blocking-rest-service-with-spring-mvc/28491c3c4f50f08939d0ac3bc36e5668d441d563/spring-mvc-asynch/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/spring-mvc-asynch/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jul 18 06:04:39 CEST 2014
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip
7 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/Application.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.boot.SpringApplication;
7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
8 | import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.ComponentScan;
11 | import org.springframework.core.task.TaskExecutor;
12 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
13 | import se.callista.springmvc.asynch.common.lambdasupport.AsyncHttpClientLambdaAware;
14 | import se.callista.springmvc.asynch.config.MyEmbeddedServletContainerCustomizer;
15 |
16 | @ComponentScan()
17 | @EnableAutoConfiguration
18 | public class Application {
19 |
20 | @SuppressWarnings("unused")
21 | private static final Logger LOG = LoggerFactory.getLogger(Application.class);
22 |
23 | @Bean
24 | public EmbeddedServletContainerCustomizer embeddedServletCustomizer(){
25 | return new MyEmbeddedServletContainerCustomizer();
26 | }
27 |
28 | @Value("${threadPool.db.init_size}")
29 | private int THREAD_POOL_DB_INIT_SIZE;
30 |
31 | @Value("${threadPool.db.max_size}")
32 | private int THREAD_POOL_DB_MAX_SIZE;
33 |
34 | @Value("${threadPool.db.queue_size}")
35 | private int THREAD_POOL_DB_QUEUE_SIZE;
36 |
37 | @Bean(name="dbThreadPoolExecutor")
38 | public TaskExecutor getTaskExecutor() {
39 | ThreadPoolTaskExecutor tpte = new ThreadPoolTaskExecutor();
40 | tpte.setCorePoolSize(THREAD_POOL_DB_INIT_SIZE);
41 | tpte.setMaxPoolSize(THREAD_POOL_DB_MAX_SIZE);
42 | tpte.setQueueCapacity(THREAD_POOL_DB_QUEUE_SIZE);
43 | tpte.initialize();
44 | return tpte;
45 | }
46 |
47 | @Bean
48 | public AsyncHttpClientLambdaAware getAsyncHttpClient() {
49 | LOG.info("### Creates a new AsyncHttpClientLambdaAware-object");
50 | return new AsyncHttpClientLambdaAware();
51 | }
52 |
53 | public static void main(String[] args) {
54 | SpringApplication.run(Application.class, args);
55 | }
56 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/deferredresult/DeferredResultStateMachineCallback.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.deferredresult;
2 |
3 | import org.springframework.web.context.request.async.DeferredResult;
4 | import se.callista.springmvc.asynch.common.statemachine.State;
5 | import se.callista.springmvc.asynch.common.statemachine.StateMachineCallback;
6 |
7 | /**
8 | * Created by magnus on 08/06/14.
9 | */
10 | public class DeferredResultStateMachineCallback implements StateMachineCallback {
11 |
12 | private DeferredResult deferredResult;
13 |
14 | public DeferredResultStateMachineCallback(DeferredResult deferredResult) {
15 | this.deferredResult = deferredResult;
16 | }
17 |
18 | @Override
19 | public void onCompleted(State state) {
20 | if (deferredResult.isSetOrExpired()) {
21 | state.getLog().logAlreadyExpiredNonBlocking();
22 |
23 | } else {
24 | boolean deferredStatus = deferredResult.setResult(state.getResult());
25 | state.getLog().logEndNonBlocking(200, deferredStatus);
26 | }
27 | }
28 |
29 | @Override
30 | public void onFailure(State state, Throwable t) {
31 | deferredResult.setErrorResult(t);
32 | state.getLog().logExceptionNonBlocking(t);
33 | }
34 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/deferredresult/DeferredResultWithBlockingWait.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.deferredresult;
2 |
3 | import org.springframework.web.context.request.async.DeferredResult;
4 |
5 | import java.util.concurrent.CountDownLatch;
6 |
7 | /**
8 | * Created by magnus on 16/05/14.
9 | */
10 | public class DeferredResultWithBlockingWait extends DeferredResult {
11 | final CountDownLatch latch = new CountDownLatch(1);
12 |
13 | @Override
14 | public boolean setResult(T result) {
15 | latch.countDown();
16 | return super.setResult(result);
17 | }
18 |
19 | public void await() {
20 | try {
21 | latch.await();
22 | } catch (InterruptedException e) {
23 | throw new RuntimeException(e);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/lambdasupport/AsyncHttpClientLambdaAware.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.lambdasupport;
2 |
3 | import com.ning.http.client.AsyncCompletionHandler;
4 | import com.ning.http.client.AsyncHttpClient;
5 | import com.ning.http.client.ListenableFuture;
6 | import com.ning.http.client.Response;
7 |
8 | import java.io.IOException;
9 |
10 | /**
11 | * Created by magnus on 18/07/14.
12 | */
13 | public class AsyncHttpClientLambdaAware {
14 |
15 | private static final AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
16 |
17 | public ListenableFuture execute(String url, final Completed c, final Error e) throws IOException {
18 | return asyncHttpClient.prepareGet(url).execute(new AsyncCompletionHandler() {
19 |
20 | @Override
21 | public Response onCompleted(Response response) throws Exception {
22 | return c.onCompleted(response);
23 | }
24 |
25 | @Override
26 | public void onThrowable(Throwable t) {
27 | e.onThrowable(t);
28 | }
29 |
30 | });
31 | };
32 |
33 | public ListenableFuture execute(String url, final Completed c) throws IOException {
34 | return asyncHttpClient.prepareGet(url).execute(new AsyncCompletionHandler() {
35 |
36 | @Override
37 | public Response onCompleted(Response response) throws Exception {
38 | return c.onCompleted(response);
39 | }
40 | });
41 | };
42 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/lambdasupport/Completed.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.lambdasupport;
2 |
3 | import com.ning.http.client.Response;
4 |
5 | /**
6 | * Created by magnus on 18/07/14.
7 | */
8 | public interface Completed {
9 | public Response onCompleted(Response response) throws Exception;
10 | }
11 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/lambdasupport/Error.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.lambdasupport;
2 |
3 | /**
4 | * Created by magnus on 18/07/14.
5 | */
6 | public interface Error {
7 | public void onThrowable(Throwable t);
8 | }
9 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/log/LogHelper.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.log;
2 |
3 | import com.sun.management.UnixOperatingSystemMXBean;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import java.lang.management.ManagementFactory;
8 | import java.lang.management.OperatingSystemMXBean;
9 | import java.util.concurrent.atomic.AtomicLong;
10 |
11 | /**
12 | * Created by magnus on 18/05/14.
13 | */
14 | public class LogHelper {
15 |
16 | private static OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
17 | private static final AtomicLong lastRequestId = new AtomicLong(0);
18 | private static final AtomicLong concurrentRequests = new AtomicLong(0);
19 | private static long maxConcurrentRequests = 0;
20 |
21 | private final String name;
22 | private final Logger log;
23 | private final int requestsPerLog;
24 | private final long reqId;
25 |
26 | public LogHelper(Class clz, String name, int requestsPerLog) {
27 | this.log = LoggerFactory.getLogger(clz);
28 | this.name = name;
29 | this.requestsPerLog = requestsPerLog;
30 | this.reqId = lastRequestId.getAndIncrement();
31 | }
32 |
33 | public void logStartBlocking() {
34 | logStart("blocking");
35 | }
36 |
37 | public void logStartNonBlocking() {
38 | logStart("non-blocking");
39 | }
40 |
41 | public void logStartProcessingStepBlocking(int processingStepNo) {
42 | logStartProcessingStep("blocking", processingStepNo);
43 | }
44 | public void logStartProcessingStepNonBlocking(int processingStepNo) {
45 | logStartProcessingStep("non-blocking", processingStepNo);
46 | }
47 |
48 | public void logAsynchProcessingStepComplete() {
49 |
50 | long concReqs = concurrentRequests.get();
51 |
52 | updateStatistics(reqId, concReqs);
53 |
54 | log.debug("{}: Asynch call complete för request #{}, hand over to the state machine for the next action", concReqs, reqId);
55 | }
56 |
57 | public void logMessage(String message) {
58 | long concReqs = concurrentRequests.get();
59 |
60 | log.debug("{}: Request #{} - {}", concReqs, reqId, message);
61 | }
62 |
63 | public void logEndProcessingStepBlocking(int processingStepNo, int httpStatus) {
64 | logEndProcessingStep("blocking", processingStepNo, httpStatus);
65 | }
66 |
67 | public void logEndProcessingStepNonBlocking(int processingStepNo, int httpStatus) {
68 | logEndProcessingStep("non-blocking", processingStepNo, httpStatus);
69 | }
70 |
71 | public void logEndBlocking(int httpStatus) {
72 | logEnd("blocking", httpStatus, null);
73 | }
74 |
75 | public void logEndNonBlocking(int httpStatus, boolean deferredStatus) {
76 | logEnd("non-blocking", httpStatus, deferredStatus);
77 | }
78 |
79 | public void logExceptionBlocking(Throwable t) {
80 | logException("blocking", t);
81 | }
82 |
83 | public void logExceptionNonBlocking(Throwable t) {
84 | logException("non-blocking", t);
85 | }
86 |
87 | public void logLeaveThreadNonBlocking() {
88 |
89 | long concReqs = concurrentRequests.get();
90 |
91 | log.debug("{}: Processing of non-blocking {} request #{}, leave the request thread", concReqs, name, reqId);
92 | }
93 |
94 | public void logAlreadyExpiredNonBlocking() {
95 |
96 | long concReqs = concurrentRequests.getAndDecrement();
97 |
98 | log.warn("{}: Processing of non-blocking {} request #{} already expired", concReqs, name, reqId);
99 | }
100 |
101 | /*
102 | * PRIVATE PARTS :-)
103 | */
104 |
105 | protected void logStart(String type) {
106 |
107 | long concReqs = concurrentRequests.getAndIncrement();
108 |
109 | updateStatistics(reqId, concReqs);
110 |
111 | log.debug("{}: Start of {} {} request #{}.", concReqs, type, name, reqId);
112 | }
113 |
114 | protected void logEnd(String type, int httpStatus, Boolean deferredStatus) {
115 |
116 | long concReqs = concurrentRequests.getAndDecrement();
117 |
118 | if (deferredStatus == null) {
119 | log.debug("{}: End of {} {} request #{}, http-status: {}", concReqs, type, name, reqId, httpStatus);
120 | } else {
121 | log.debug("{}: End of {} {} request #{}, http-status: {}, deferred-status: {}", concReqs, type, name, reqId, httpStatus, deferredStatus);
122 | }
123 | }
124 |
125 | protected void logStartProcessingStep(String type, int processingStepNo) {
126 |
127 | long concReqs = concurrentRequests.getAndIncrement();
128 |
129 | log.debug("{}: Start processing of {} call #{} in request #{}", concReqs, type, processingStepNo, reqId);
130 | }
131 |
132 | protected void logEndProcessingStep(String type, int processingStepNo, int httpStatus) {
133 |
134 | long concReqs = concurrentRequests.getAndDecrement();
135 |
136 | log.debug("{}: End of processing of {} call #{} in request #{}, http-status: {}", concReqs, type, processingStepNo, reqId, httpStatus);
137 | }
138 |
139 | protected void logException(String type, Throwable t) {
140 |
141 | long concReqs = concurrentRequests.getAndDecrement();
142 |
143 | log.warn("{}: Processing of {} {} request #{} caused an exception: {}", concReqs, type, name, reqId, t);
144 | }
145 | protected void updateStatistics(long reqId, long concReqs) {
146 | if (concReqs > maxConcurrentRequests) {
147 | maxConcurrentRequests = concReqs;
148 | }
149 |
150 | if (reqId % requestsPerLog == 0 && reqId > 0) {
151 | Object openFiles = "UNKNOWN";
152 | if (os instanceof UnixOperatingSystemMXBean) {
153 | openFiles = ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount();
154 | }
155 | log.info("Statistics: noOfReqs: {}, maxConcReqs: {}, openFiles: {}", reqId, maxConcurrentRequests, openFiles);
156 | }
157 | }
158 |
159 | }
160 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/log/LogHelperFactory.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.log;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * Created by magnus on 30/05/14.
10 | */
11 | @Component
12 | public class LogHelperFactory {
13 | private @Autowired AutowireCapableBeanFactory beanFactory;
14 |
15 | @Value("${statistics.requestsPerLog}")
16 | private int STAT_REQS_PER_LOG;
17 |
18 | public LogHelper getLog(Class clz, String name) {
19 | return new LogHelper(clz, name, STAT_REQS_PER_LOG);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/statemachine/Processor.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.statemachine;
2 |
3 | /**
4 | * Created by magnus on 15/05/14.
5 | */
6 | public interface Processor {
7 | public void process(State state);
8 | }
9 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/statemachine/State.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.statemachine;
2 |
3 | import se.callista.springmvc.asynch.common.log.LogHelper;
4 | import se.callista.springmvc.asynch.common.statemachine.internal.StateProcessingStepIterator;
5 |
6 | import java.util.Iterator;
7 | import java.util.concurrent.atomic.AtomicLong;
8 |
9 | /**
10 | * Created by magnus on 15/05/14.
11 | */
12 | public class State {
13 |
14 | private static final AtomicLong lastProcessId = new AtomicLong(0);
15 |
16 | private StateProcessingStepIterator processingSteps;
17 | private long processId;
18 | private LogHelper log;
19 | private StateMachineCallback completionCallback;
20 | private String result = "";
21 |
22 | public State(Iterator processingSteps, LogHelper log, StateMachineCallback completionCallback) {
23 | this.processingSteps = new StateProcessingStepIterator(processingSteps);
24 | this.log = log;
25 | this.completionCallback = completionCallback;
26 | this.processId = lastProcessId.incrementAndGet();
27 | }
28 |
29 | public LogHelper getLog() {
30 | return log;
31 | }
32 | public long getProcessId() {
33 | return processId;
34 | }
35 |
36 | public Iterator getProcessingSteps() {
37 | return processingSteps;
38 | }
39 | public int getProcessingStepNo() { return processingSteps.getProcessingStepNo(); }
40 |
41 | public StateMachineCallback getCompletionCallback() {
42 | return completionCallback;
43 | }
44 |
45 | public String appendResult(String newResult) {
46 | return result += newResult;
47 | }
48 | public String getResult() {
49 | return result;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/statemachine/StateMachine.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.statemachine;
2 |
3 | import org.springframework.stereotype.Component;
4 | import se.callista.springmvc.asynch.common.log.LogHelper;
5 |
6 | import java.util.Iterator;
7 |
8 | /**
9 | * A stateless minimal state machine
10 | */
11 | @Component
12 | public class StateMachine {
13 |
14 | public void initProcessing(Iterator processingSteps, LogHelper log, StateMachineCallback completionCallback) {
15 | executeNextStep(new State(processingSteps, log, completionCallback));
16 | }
17 |
18 | public void executeNextStep(State state) {
19 |
20 | if (state.getProcessingSteps().hasNext()) {
21 | // Initiate next processing step...
22 | state.getProcessingSteps().next().process(state);
23 |
24 | } else {
25 | // We are done...
26 | state.getCompletionCallback().onCompleted(state);
27 | }
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/statemachine/StateMachineCallback.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.statemachine;
2 |
3 | /**
4 | * Created by magnus on 29/05/14.
5 | */
6 | public interface StateMachineCallback {
7 | void onCompleted(State state);
8 | void onFailure(State state, Throwable t);
9 | }
10 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/common/statemachine/internal/StateProcessingStepIterator.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.statemachine.internal;
2 |
3 | import se.callista.springmvc.asynch.common.statemachine.Processor;
4 |
5 | import java.util.Iterator;
6 |
7 | /**
8 | * Created by magnus on 08/06/14.
9 | */
10 | public class StateProcessingStepIterator implements Iterator {
11 |
12 | private final Iterator processingSteps;
13 | private int processingStepNo = 0;
14 |
15 | public StateProcessingStepIterator(Iterator processingSteps) {
16 | this.processingSteps = processingSteps;
17 | }
18 |
19 | @Override
20 | public boolean hasNext() {
21 | return processingSteps.hasNext();
22 | }
23 |
24 | @Override
25 | public Processor next() {
26 | if (processingSteps.hasNext()) processingStepNo++;
27 | return processingSteps.next();
28 | }
29 |
30 | public int getProcessingStepNo() {
31 | return processingStepNo;
32 | }
33 |
34 | @Override
35 | public void remove() {
36 | processingSteps.remove();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/config/MyEmbeddedServletContainerCustomizer.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.config;
2 |
3 | import org.apache.catalina.connector.Connector;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Value;
7 | import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
8 | import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
9 | import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
10 | import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
11 |
12 | public class MyEmbeddedServletContainerCustomizer implements EmbeddedServletContainerCustomizer {
13 |
14 | private static final Logger LOG = LoggerFactory.getLogger(MyEmbeddedServletContainerCustomizer.class);
15 |
16 | @Value("${servlet.container.maxThreads}")
17 | private int MAX_THREADS;
18 |
19 | @Override
20 | public void customize(ConfigurableEmbeddedServletContainer factory) {
21 | if(factory instanceof TomcatEmbeddedServletContainerFactory) {
22 | customizeTomcat((TomcatEmbeddedServletContainerFactory) factory);
23 | }
24 | }
25 |
26 | public void customizeTomcat(TomcatEmbeddedServletContainerFactory factory) {
27 | factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
28 | @Override
29 | public void customize(Connector connector) {
30 | Object defaultMaxThreads = connector.getAttribute("maxThreads");
31 | connector.setAttribute("maxThreads", MAX_THREADS);
32 | LOG.info("Changed Tomcat connector maxThreads from " + defaultMaxThreads + " to " + MAX_THREADS);
33 | }
34 | });
35 | }
36 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/aggregator/blocking/AggregatorBlockingController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.aggregator.blocking;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.http.HttpStatus;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestParam;
9 | import org.springframework.web.bind.annotation.RestController;
10 | import org.springframework.web.client.RestTemplate;
11 | import se.callista.springmvc.asynch.common.log.LogHelper;
12 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
13 |
14 | import javax.annotation.PostConstruct;
15 |
16 | @RestController
17 | public class AggregatorBlockingController {
18 |
19 | private static LogHelper LOG;
20 |
21 | private RestTemplate restTemplate = new RestTemplate();
22 |
23 | @Autowired
24 | private LogHelperFactory logFactory;
25 |
26 | @Value("${sp.non_blocking.url}")
27 | private String SP_NON_BLOCKING_URL;
28 |
29 | @PostConstruct
30 | public void initAfterInject() {
31 | LOG = logFactory.getLog(AggregatorBlockingController.class, "aggregator");
32 | }
33 |
34 | /**
35 | * Sample usage: curl "http://localhost:9080/aggregate-blocking?minMs=1000&maxMs=2000"
36 | *
37 | * @param dbLookupMs
38 | * @param dbHits
39 | * @param minMs
40 | * @param maxMs
41 | * @return
42 | */
43 | @RequestMapping("/aggregate-blocking")
44 | public String blockingAggregator(
45 | @RequestParam(value = "dbLookupMs", required = false, defaultValue = "200") int dbLookupMs,
46 | @RequestParam(value = "dbHits", required = false, defaultValue = "3") int dbHits,
47 | @RequestParam(value = "minMs", required = false, defaultValue = "0") int minMs,
48 | @RequestParam(value = "maxMs", required = false, defaultValue = "0") int maxMs) {
49 |
50 | DbLookup dbLookup = new DbLookup(LOG, dbLookupMs, dbHits);
51 |
52 | HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
53 |
54 | LOG.logStartBlocking();
55 |
56 | String aggregatedResult = "";
57 | try {
58 | int noOfCalls = dbLookup.executeDbLookup();
59 |
60 | for (int i = 0; i < noOfCalls; i++) {
61 | LOG.logStartProcessingStepBlocking(i);
62 | ResponseEntity result = restTemplate.getForEntity(
63 | SP_NON_BLOCKING_URL + "?minMs={minMs}&maxMs={maxMs}", String.class, minMs, maxMs);
64 |
65 | // TODO: Handle status codes other than 200...
66 | status = result.getStatusCode();
67 | LOG.logEndProcessingStepBlocking(i, status.value());
68 |
69 | aggregatedResult += result.getBody() + '\n';
70 | }
71 |
72 | return aggregatedResult;
73 |
74 | } finally {
75 | LOG.logEndBlocking(status.value());
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/aggregator/blocking/DbLookup.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.aggregator.blocking;
2 |
3 | import se.callista.springmvc.asynch.common.log.LogHelper;
4 |
5 | /**
6 | * Created by magnus on 20/07/14.
7 | */
8 | public class DbLookup {
9 | private final LogHelper LOG;
10 | private final int dbLookupMs;
11 | private final int dbHits;
12 |
13 | public DbLookup(LogHelper log, int dbLookupMs, int dbHits) {
14 | LOG = log;
15 | this.dbLookupMs = dbLookupMs;
16 | this.dbHits = dbHits;
17 | }
18 |
19 | public int executeDbLookup() {
20 |
21 | LOG.logMessage("Start of blocking dbLookup");
22 | int hits = simulateDbLookup();
23 | LOG.logMessage("Processing of blocking dbLookup done");
24 |
25 | return hits;
26 | }
27 |
28 | // Simulate a blocking db-lookup by putting the current thread to sleep for a while...
29 | protected int simulateDbLookup(){
30 |
31 | try {
32 | Thread.sleep(dbLookupMs);
33 | } catch (InterruptedException e) {}
34 |
35 | return dbHits;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/aggregator/nonblocking/callback/AggregatorCallback.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.aggregator.nonblocking.callback;
2 |
3 | import com.ning.http.client.AsyncCompletionHandler;
4 | import com.ning.http.client.Response;
5 |
6 | public class AggregatorCallback extends AsyncCompletionHandler {
7 |
8 | private final int id;
9 | private final AggregatorEventHandler eventHandler;
10 |
11 | public AggregatorCallback(int id, AggregatorEventHandler eventHandler) {
12 | this.id = id;
13 | this.eventHandler = eventHandler;
14 | }
15 |
16 | @Override
17 | public Response onCompleted(Response response) throws Exception{
18 | eventHandler.onResult(id, response);
19 | return response;
20 | }
21 |
22 | @Override
23 | public void onThrowable(Throwable t){
24 | eventHandler.onError(id, t);
25 | }
26 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/aggregator/nonblocking/callback/AggregatorEventHandler.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.aggregator.nonblocking.callback;
2 |
3 | import com.ning.http.client.AsyncHttpClient;
4 | import com.ning.http.client.ListenableFuture;
5 | import com.ning.http.client.Response;
6 | import org.springframework.web.context.request.async.DeferredResult;
7 | import se.callista.springmvc.asynch.common.log.LogHelper;
8 |
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.Timer;
13 | import java.util.TimerTask;
14 | import java.util.concurrent.CancellationException;
15 | import java.util.concurrent.atomic.AtomicInteger;
16 |
17 | /**
18 | * Created by magnus on 22/04/14.
19 | */
20 | public class AggregatorEventHandler {
21 |
22 | private Timer timer = new Timer();
23 |
24 | private final LogHelper log;
25 |
26 | // @Value("${sp.non_blocking.url}")
27 | private String SP_NON_BLOCKING_URL;
28 |
29 | private final int noOfCalls;
30 | private final int maxMs;
31 | private final int minMs;
32 | private final int timeoutMs;
33 | private final DeferredResult deferredResult;
34 |
35 | private final static AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
36 |
37 | private final AtomicInteger noOfResults = new AtomicInteger(0);
38 | private String result = "";
39 |
40 | private List> executors = new ArrayList<>();
41 |
42 | public AggregatorEventHandler(LogHelper log, int noOfCalls, String url, int minMs, int maxMs, int timeoutMs, DeferredResult deferredResult) {
43 | this.log = log;
44 | this.noOfCalls = noOfCalls;
45 | this.SP_NON_BLOCKING_URL = url;
46 | this.minMs = minMs;
47 | this.maxMs = maxMs;
48 | this.timeoutMs = timeoutMs;
49 | this.deferredResult = deferredResult;
50 | }
51 |
52 | public void onStart() {
53 | try {
54 | for (int i = 0; i < noOfCalls; i++) {
55 | log.logStartProcessingStepNonBlocking(i);
56 | String url = SP_NON_BLOCKING_URL + "?minMs=" + minMs + "&maxMs=" + maxMs;
57 | executors.add(asyncHttpClient.prepareGet(url).execute(new AggregatorCallback(i, this)));
58 | }
59 | } catch (IOException e) {
60 | throw new RuntimeException(e);
61 | }
62 |
63 | // Setup a timer for the max wait-period
64 | log.logMessage("Start timer for: " + timeoutMs + " ms.");
65 | TimerTask timeoutTask = new TimerTask() {
66 |
67 | @Override
68 | public void run() {
69 | onTimeout();
70 | }
71 | };
72 |
73 | // Schedule the timeout task
74 | timer.schedule(timeoutTask, timeoutMs);
75 | }
76 |
77 | public void onResult(int id, Response response) {
78 |
79 | try {
80 | // TODO: Handle status codes other than 200...
81 | int httpStatus = response.getStatusCode();
82 | log.logEndProcessingStepNonBlocking(id, httpStatus);
83 |
84 | // If many requests completes at the same time the following code must be executed in sequence for one thread at a time
85 | // Since we don't have any Actor-like mechanism to rely on (for the time being...) we simply ensure that the code block is executed by one thread at a time by an old school synchronized block
86 | // Since the processing in the block is very limited it will not cause a bottleneck.
87 | synchronized (result) {
88 | // Count down, aggregate answer and return if all answers (also cancel timer)...
89 | int noOfRes = noOfResults.incrementAndGet();
90 |
91 | // Perform the aggregation...
92 | log.logMessage("Safely adding response #" + id);
93 | result += response.getResponseBody() + '\n';
94 |
95 | if (noOfRes >= noOfCalls) {
96 | onAllCompleted();
97 | }
98 | }
99 |
100 |
101 |
102 | } catch (IOException e) {
103 | throw new RuntimeException(e);
104 | }
105 | }
106 |
107 | public void onError(int id, Throwable t) {
108 |
109 | // Skip logging an error if we just canceled the request due to an timeout
110 | if (t instanceof CancellationException) return;
111 |
112 | log.logExceptionNonBlocking(t);
113 |
114 | // Count down, aggregate answer and return if all answers (also cancel timer)...
115 | int noOfRes = noOfResults.incrementAndGet();
116 | result += t.toString() + '\n';
117 |
118 | if (noOfRes >= noOfCalls) {
119 | onAllCompleted();
120 | }
121 |
122 | // TODO: Handle asynchronous processing errors...
123 |
124 | }
125 |
126 | public void onTimeout() {
127 |
128 | // complete missing answers and return ...
129 | log.logMessage("Timeout in aggregating service, only received answers from " + noOfResults.get() + " out of total " + noOfCalls + " expected responses.");
130 | int i = 0;
131 | for (ListenableFuture executor : executors) {
132 | if (!executor.isDone()) {
133 | log.logMessage("Cancel asych request #" + i);
134 | executor.cancel(true);
135 | }
136 | i++;
137 | }
138 | onAllCompleted();
139 | }
140 |
141 | public void onAllCompleted() {
142 | log.logMessage("All done, cancel timer");
143 | timer.cancel();
144 |
145 | if (deferredResult.isSetOrExpired()) {
146 | log.logAlreadyExpiredNonBlocking();
147 | } else {
148 | boolean deferredStatus = deferredResult.setResult(result);
149 | log.logEndNonBlocking(200, deferredStatus);
150 | }
151 | }
152 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/aggregator/nonblocking/callback/AggregatorNonBlockingCallbackController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.aggregator.nonblocking.callback;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Qualifier;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.core.task.TaskExecutor;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestParam;
9 | import org.springframework.web.bind.annotation.RestController;
10 | import org.springframework.web.context.request.async.DeferredResult;
11 | import se.callista.springmvc.asynch.common.log.LogHelper;
12 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
13 |
14 | import javax.annotation.PostConstruct;
15 | import java.io.IOException;
16 |
17 | @RestController
18 | public class AggregatorNonBlockingCallbackController {
19 |
20 | private static LogHelper LOG;
21 |
22 | @Autowired
23 | private LogHelperFactory logFactory;
24 |
25 | @Value("${sp.non_blocking.url}")
26 | private String SP_NON_BLOCKING_URL;
27 |
28 | @Value("${aggregator.timeoutMs}")
29 | private int TIMEOUT_MS;
30 |
31 | @PostConstruct
32 | public void initAfterInject() {
33 | LOG = logFactory.getLog(AggregatorNonBlockingCallbackController.class, "aggregator");
34 | }
35 |
36 | @Autowired
37 | @Qualifier("dbThreadPoolExecutor")
38 | private TaskExecutor dbThreadPoolExecutor;
39 |
40 | /**
41 | * Sample usage: curl "http://localhost:9080/aggregate-non-blocking-callback?minMs=1000&maxMs=2000"
42 | *
43 | * @param dbLookupMs
44 | * @param dbHits
45 | * @param minMs
46 | * @param maxMs
47 | * @return
48 | * @throws IOException
49 | */
50 | @RequestMapping("/aggregate-non-blocking-callback")
51 | public DeferredResult nonBlockingAggregator(
52 | @RequestParam(value = "dbLookupMs", required = false, defaultValue = "0") int dbLookupMs,
53 | @RequestParam(value = "dbHits", required = false, defaultValue = "3") int dbHits,
54 | @RequestParam(value = "minMs", required = false, defaultValue = "0") int minMs,
55 | @RequestParam(value = "maxMs", required = false, defaultValue = "0") int maxMs) throws IOException {
56 |
57 | LOG.logStartNonBlocking();
58 |
59 | DeferredResult deferredResult = new DeferredResult();
60 |
61 | dbThreadPoolExecutor.execute(new DbLookupRunnable(LOG, dbLookupMs, dbHits, SP_NON_BLOCKING_URL, minMs, maxMs, TIMEOUT_MS, deferredResult));
62 |
63 | LOG.logLeaveThreadNonBlocking();
64 |
65 | // Return to let go of the precious thread we are holding on to...
66 | return deferredResult;
67 | }
68 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/aggregator/nonblocking/callback/DbLookupRunnable.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.aggregator.nonblocking.callback;
2 |
3 | import org.springframework.web.context.request.async.DeferredResult;
4 | import se.callista.springmvc.asynch.common.log.LogHelper;
5 |
6 | public class DbLookupRunnable implements Runnable {
7 |
8 | private final LogHelper log;
9 |
10 | private int dbLookupMs;
11 | private int dbHits;
12 | private final int maxMs;
13 | private final int minMs;
14 | private final int timeoutMs;
15 | private DeferredResult deferredResult;
16 | private String url;
17 |
18 | public DbLookupRunnable(LogHelper log, int dbLookupMs, int dbHits, String url, int minMs, int maxMs, int timeoutMs, DeferredResult deferredResult) {
19 | this.log = log;
20 | this.dbLookupMs = dbLookupMs;
21 | this.dbHits = dbHits;
22 | this.url = url;
23 | this.minMs = minMs;
24 | this.maxMs = maxMs;
25 | this.timeoutMs = timeoutMs;
26 | this.deferredResult = deferredResult;
27 | }
28 |
29 | @Override
30 | public void run() {
31 | //seconds later in another thread...
32 | int noOfCalls = execute();
33 | AggregatorEventHandler aeh = new AggregatorEventHandler(log, noOfCalls, url, minMs, maxMs, timeoutMs, deferredResult);
34 | aeh.onStart();
35 | }
36 |
37 | public int execute() {
38 |
39 | log.logMessage("Start of blocking dbLookup");
40 | int hits = simulateDbLookup();
41 | log.logMessage("Processing of blocking dbLookup done");
42 |
43 | return hits;
44 | }
45 |
46 | protected int simulateDbLookup(){
47 |
48 | try {
49 | Thread.sleep(dbLookupMs);
50 | } catch (InterruptedException e) {}
51 |
52 | return dbHits;
53 | }
54 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/aggregator/nonblocking/lambda/AggregatorNonBlockingLambdaController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.aggregator.nonblocking.lambda;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Qualifier;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.core.task.TaskExecutor;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestParam;
9 | import org.springframework.web.bind.annotation.RestController;
10 | import org.springframework.web.context.request.async.DeferredResult;
11 | import se.callista.springmvc.asynch.common.log.LogHelper;
12 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
13 |
14 | import javax.annotation.PostConstruct;
15 | import java.io.IOException;
16 |
17 | @RestController
18 | public class AggregatorNonBlockingLambdaController {
19 |
20 | private static LogHelper LOG;
21 |
22 | @Autowired
23 | private LogHelperFactory logFactory;
24 |
25 | @Value("${sp.non_blocking.url}")
26 | private String SP_NON_BLOCKING_URL;
27 |
28 | @Value("${aggregator.timeoutMs}")
29 | private int TIMEOUT_MS;
30 |
31 | @PostConstruct
32 | public void initAfterInject() {
33 | LOG = logFactory.getLog(AggregatorNonBlockingLambdaController.class, "aggregator");
34 | }
35 |
36 | @Autowired
37 | @Qualifier("dbThreadPoolExecutor")
38 | private TaskExecutor dbThreadPoolExecutor;
39 |
40 | /**
41 | * Sample usage: curl "http://localhost:9080/aggregate-non-blocking-lambda?minMs=1000&maxMs=2000"
42 | *
43 | * @param dbLookupMs
44 | * @param dbHits
45 | * @param minMs
46 | * @param maxMs
47 | * @return
48 | * @throws java.io.IOException
49 | */
50 | @RequestMapping("/aggregate-non-blocking-lambda")
51 | public DeferredResult nonBlockingAggregator(
52 | @RequestParam(value = "dbLookupMs", required = false, defaultValue = "0") int dbLookupMs,
53 | @RequestParam(value = "dbHits", required = false, defaultValue = "3") int dbHits,
54 | @RequestParam(value = "minMs", required = false, defaultValue = "0") int minMs,
55 | @RequestParam(value = "maxMs", required = false, defaultValue = "0") int maxMs) throws IOException {
56 |
57 | // Delegate the whole processing to a executor-instance to avoid concurrency problems with other concurrent requests
58 | Executor exec = new Executor(LOG, SP_NON_BLOCKING_URL, TIMEOUT_MS, dbThreadPoolExecutor, dbLookupMs, dbHits, minMs, maxMs);
59 |
60 | DeferredResult deferredResult = exec.startNonBlockingExecution();
61 |
62 | // Return to let go of the precious thread we are holding on to...
63 | return deferredResult;
64 | }
65 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/aggregator/nonblocking/lambda/DbLookup.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.aggregator.nonblocking.lambda;
2 |
3 | import se.callista.springmvc.asynch.common.log.LogHelper;
4 |
5 | /**
6 | * Created by magnus on 20/07/14.
7 | */
8 | public class DbLookup {
9 | private final LogHelper LOG;
10 | private final int dbLookupMs;
11 | private final int dbHits;
12 |
13 | public DbLookup(LogHelper log, int dbLookupMs, int dbHits) {
14 | LOG = log;
15 | this.dbLookupMs = dbLookupMs;
16 | this.dbHits = dbHits;
17 | }
18 |
19 | public int executeDbLookup() {
20 |
21 | LOG.logMessage("Start of blocking dbLookup");
22 | int hits = simulateDbLookup();
23 | LOG.logMessage("Processing of blocking dbLookup done");
24 |
25 | return hits;
26 | }
27 |
28 | // Simulate a blocking db-lookup by putting the current thread to sleep for a while...
29 | protected int simulateDbLookup(){
30 |
31 | try {
32 | Thread.sleep(dbLookupMs);
33 | } catch (InterruptedException e) {}
34 |
35 | return dbHits;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/aggregator/nonblocking/lambda/Executor.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.aggregator.nonblocking.lambda;
2 |
3 | import com.ning.http.client.ListenableFuture;
4 | import com.ning.http.client.Response;
5 | import org.springframework.core.task.TaskExecutor;
6 | import org.springframework.web.context.request.async.DeferredResult;
7 | import se.callista.springmvc.asynch.common.lambdasupport.AsyncHttpClientLambdaAware;
8 | import se.callista.springmvc.asynch.common.log.LogHelper;
9 |
10 | import java.io.IOException;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 | import java.util.Timer;
14 | import java.util.TimerTask;
15 | import java.util.concurrent.atomic.AtomicInteger;
16 |
17 | /**
18 | * Created by magnus on 20/07/14.
19 | */
20 | public class Executor {
21 |
22 | final private LogHelper log;
23 | final private String baseUrl;
24 | final private int timeoutMs;
25 | final private TaskExecutor dbThreadPoolExecutor;
26 | final private int dbLookupMs;
27 | final private int dbHits;
28 | final private int minMs;
29 | final private int maxMs;
30 |
31 | private static final AsyncHttpClientLambdaAware asyncHttpClient = new AsyncHttpClientLambdaAware();
32 | private static Timer timer = new Timer();
33 | private TimerTask timeoutTask = null;
34 | private List> concurrentExecutors = new ArrayList<>();
35 | final AtomicInteger noOfResults = new AtomicInteger(0);
36 | private int noOfCalls = 0;
37 | private DeferredResult deferredResult = null;
38 | private List resultArr = new ArrayList<>();
39 |
40 | public Executor(LogHelper log, String baseUrl, int timeoutMs, TaskExecutor dbThreadPoolExecutor, int dbLookupMs, int dbHits, int minMs, int maxMs) {
41 | this.log = log;
42 | this.baseUrl = baseUrl;
43 | this.timeoutMs = timeoutMs;
44 | this.dbThreadPoolExecutor = dbThreadPoolExecutor;
45 | this.dbLookupMs = dbLookupMs;
46 | this.dbHits = dbHits;
47 | this.minMs = minMs;
48 | this.maxMs = maxMs;
49 | }
50 |
51 | public DeferredResult startNonBlockingExecution() {
52 | log.logStartNonBlocking();
53 |
54 | DbLookup dbLookup = new DbLookup(log, dbLookupMs, dbHits);
55 | deferredResult = new DeferredResult();
56 |
57 | dbThreadPoolExecutor.execute(() -> {
58 | noOfCalls = dbLookup.executeDbLookup();
59 | try {
60 | for (int i = 0; i < noOfCalls; i++) {
61 | final int id = i+1;
62 |
63 | log.logStartProcessingStepNonBlocking(id);
64 | String url = baseUrl + "?minMs=" + minMs + "&maxMs=" + maxMs;
65 | concurrentExecutors.add(asyncHttpClient.execute(url, (response) -> {
66 |
67 | // TODO: Handle status codes other than 200...
68 | int httpStatus = response.getStatusCode();
69 | log.logEndProcessingStepNonBlocking(id, httpStatus);
70 |
71 | // If many requests completes at the same time the following code must be executed in sequence for one thread at a time
72 | // Since we don't have any Actor-like mechanism to rely on (for the time being...) we simply ensure that the code block is executed by one thread at a time by an old school synchronized block
73 | // Since the processing in the block is very limited it will not cause a bottleneck.
74 | synchronized (resultArr) {
75 | // Count down, aggregate answer and return if all answers (also cancel timer)...
76 | int noOfRes = noOfResults.incrementAndGet();
77 |
78 | // Perform the aggregation...
79 | log.logMessage("Safely adding response #" + id);
80 | resultArr.add(response.getResponseBody());
81 |
82 | if (noOfRes >= noOfCalls) {
83 | onAllCompleted();
84 | }
85 | }
86 | return response;
87 | }));
88 | }
89 | } catch (IOException e) {
90 | throw new RuntimeException(e);
91 | }
92 |
93 | // Setup a timer for the max wait-period
94 | log.logMessage("Start timer for: " + timeoutMs + " ms.");
95 | timeoutTask = new TimerTask() {
96 |
97 | @Override
98 | public void run() {
99 | onTimeout();
100 | }
101 | };
102 |
103 | // Schedule the timeout task
104 | timer.schedule(timeoutTask, timeoutMs);
105 |
106 | });
107 |
108 | // Ok, everything is now setup for asynchronous processing, let the play begin...
109 | log.logLeaveThreadNonBlocking();
110 |
111 | return deferredResult;
112 | }
113 |
114 | private void onTimeout() {
115 |
116 | // complete missing answers and return ...
117 | log.logMessage("Timeout in aggregating service, only received answers from " + noOfResults.get() + " out of total " + noOfCalls + " expected responses.");
118 | int i = 0;
119 | for (ListenableFuture executor : concurrentExecutors) {
120 | if (!executor.isDone()) {
121 | log.logMessage("Cancel asych request #" + i);
122 | executor.cancel(true);
123 | }
124 | i++;
125 | }
126 | onAllCompleted();
127 | }
128 |
129 | private void onAllCompleted() {
130 | log.logMessage("All done, cancel timer");
131 | timeoutTask.cancel();
132 |
133 | if (deferredResult.isSetOrExpired()) {
134 | log.logAlreadyExpiredNonBlocking();
135 | } else {
136 | boolean deferredStatus = deferredResult.setResult(getTotalResult());
137 | log.logEndNonBlocking(200, deferredStatus);
138 | }
139 | }
140 |
141 | private String getTotalResult() {
142 | String totalResult = "";
143 | for (String r : resultArr)
144 | totalResult += r + '\n';
145 | return totalResult;
146 | }
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/router/blocking/RouterBlockingController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.router.blocking;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.http.HttpStatus;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestParam;
9 | import org.springframework.web.bind.annotation.RestController;
10 | import org.springframework.web.client.RestTemplate;
11 | import se.callista.springmvc.asynch.common.log.LogHelper;
12 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
13 |
14 | import javax.annotation.PostConstruct;
15 |
16 | @RestController
17 | public class RouterBlockingController {
18 |
19 | private LogHelper LOG;
20 |
21 | private RestTemplate restTemplate = new RestTemplate();
22 |
23 | @Autowired
24 | private LogHelperFactory logFactory;
25 |
26 | @Value("${sp.non_blocking.url}")
27 | private String SP_NON_BLOCKING_URL;
28 |
29 | @PostConstruct
30 | public void initAfterInject() {
31 | LOG = logFactory.getLog(RouterBlockingController.class, "router");
32 | }
33 |
34 | /**
35 | * Sample usage: curl "http://localhost:9080/router-blocking?minMs=1000&maxMs=2000"
36 | *
37 | * @param minMs
38 | * @param maxMs
39 | * @return
40 | */
41 | @RequestMapping("/router-blocking")
42 | public String blockingRouter(
43 | @RequestParam(value = "minMs", required = false, defaultValue = "0") int minMs,
44 | @RequestParam(value = "maxMs", required = false, defaultValue = "0") int maxMs) {
45 |
46 | HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
47 |
48 | LOG.logStartBlocking();
49 |
50 | try {
51 | String url = SP_NON_BLOCKING_URL + "?minMs={minMs}&maxMs={maxMs}";
52 | ResponseEntity result = restTemplate.getForEntity(url, String.class, minMs, maxMs);
53 |
54 | // TODO: Handle status codes other than 200...
55 | status = result.getStatusCode();
56 |
57 | return result.getBody();
58 |
59 | } finally {
60 | LOG.logEndBlocking(status.value());
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/router/nonblocking/anonymous/RouterNonBlockingAnonymousController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.router.nonblocking.anonymous;
2 |
3 | import com.ning.http.client.AsyncCompletionHandler;
4 | import com.ning.http.client.AsyncHttpClient;
5 | import com.ning.http.client.Response;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import org.springframework.web.bind.annotation.RequestParam;
10 | import org.springframework.web.bind.annotation.RestController;
11 | import org.springframework.web.context.request.async.DeferredResult;
12 | import se.callista.springmvc.asynch.common.log.LogHelper;
13 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
14 |
15 | import javax.annotation.PostConstruct;
16 | import java.io.IOException;
17 |
18 | @RestController
19 | public class RouterNonBlockingAnonymousController {
20 |
21 | private LogHelper LOG;
22 |
23 | private static final AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
24 |
25 | @Autowired
26 | private LogHelperFactory logFactory;
27 |
28 | @Value("${sp.non_blocking.url}")
29 | private String SP_NON_BLOCKING_URL;
30 |
31 | @PostConstruct
32 | public void initAfterInject() {
33 | LOG = logFactory.getLog(RouterNonBlockingAnonymousController.class, "router");
34 | }
35 |
36 | /**
37 | * Sample usage: curl "http://localhost:9080/router-non-blocking-anonymous?minMs=1000&maxMs=2000"
38 | *
39 | * @param minMs
40 | * @param maxMs
41 | * @return
42 | * @throws java.io.IOException
43 | */
44 | @RequestMapping("/router-non-blocking-anonymous")
45 | public DeferredResult nonBlockingRouter(
46 | @RequestParam(value = "minMs", required = false, defaultValue = "0") int minMs,
47 | @RequestParam(value = "maxMs", required = false, defaultValue = "0") int maxMs) throws IOException {
48 |
49 | LOG.logStartNonBlocking();
50 |
51 | final DeferredResult deferredResult = new DeferredResult<>();
52 |
53 | String url = SP_NON_BLOCKING_URL + "?minMs=" + minMs + "&maxMs=" + maxMs;
54 |
55 | asyncHttpClient.prepareGet(url).execute(
56 | new AsyncCompletionHandler() {
57 | @Override
58 | public Response onCompleted(Response response) throws Exception {
59 | // TODO: Handle status codes other than 200...
60 | int httpStatus = response.getStatusCode();
61 |
62 | if (deferredResult.isSetOrExpired()) {
63 | LOG.logAlreadyExpiredNonBlocking();
64 |
65 | } else {
66 | boolean deferredStatus = deferredResult.setResult(response.getResponseBody());
67 | LOG.logEndNonBlocking(httpStatus, deferredStatus);
68 | }
69 | return response;
70 | }
71 |
72 | @Override
73 | public void onThrowable(Throwable t){
74 |
75 | // TODO: Handle asynchronous processing errors...
76 |
77 | if (deferredResult.isSetOrExpired()) {
78 | LOG.logExceptionNonBlocking(t);
79 | }
80 | }
81 | });
82 |
83 | LOG.logLeaveThreadNonBlocking();
84 |
85 | // Return to let go of the precious thread we are holding on to...
86 | return deferredResult;
87 | }
88 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/router/nonblocking/callback/RouterCallback.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.router.nonblocking.callback;
2 |
3 | import com.ning.http.client.AsyncCompletionHandler;
4 | import com.ning.http.client.Response;
5 | import org.springframework.web.context.request.async.DeferredResult;
6 | import se.callista.springmvc.asynch.common.log.LogHelper;
7 |
8 | public class RouterCallback extends AsyncCompletionHandler {
9 |
10 | private final LogHelper log;
11 |
12 | private DeferredResult deferredResult;
13 |
14 |
15 | public RouterCallback(LogHelper log, DeferredResult deferredResult) {
16 | this.log = log;
17 | this.deferredResult = deferredResult;
18 | }
19 |
20 | @Override
21 | public Response onCompleted(Response response) throws Exception{
22 |
23 | // TODO: Handle status codes other than 200...
24 | int httpStatus = response.getStatusCode();
25 |
26 | if (deferredResult.isSetOrExpired()) {
27 | log.logAlreadyExpiredNonBlocking();
28 |
29 | } else {
30 | boolean deferredStatus = deferredResult.setResult(response.getResponseBody());
31 | log.logEndNonBlocking(httpStatus, deferredStatus);
32 | }
33 | return response;
34 | }
35 |
36 | @Override
37 | public void onThrowable(Throwable t){
38 |
39 | // TODO: Handle asynchronous processing errors...
40 |
41 | if (deferredResult.isSetOrExpired()) {
42 | log.logExceptionNonBlocking(t);
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/router/nonblocking/callback/RouterNonBlockingCallbackController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.router.nonblocking.callback;
2 |
3 | import com.ning.http.client.AsyncHttpClient;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.web.bind.annotation.RequestMapping;
7 | import org.springframework.web.bind.annotation.RequestParam;
8 | import org.springframework.web.bind.annotation.RestController;
9 | import org.springframework.web.context.request.async.DeferredResult;
10 | import se.callista.springmvc.asynch.common.log.LogHelper;
11 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
12 |
13 | import javax.annotation.PostConstruct;
14 | import java.io.IOException;
15 |
16 | @RestController
17 | public class RouterNonBlockingCallbackController {
18 |
19 | private LogHelper LOG;
20 |
21 | private static final AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
22 |
23 | @Autowired
24 | private LogHelperFactory logFactory;
25 |
26 | @Value("${sp.non_blocking.url}")
27 | private String SP_NON_BLOCKING_URL;
28 |
29 | @PostConstruct
30 | public void initAfterInject() {
31 | LOG = logFactory.getLog(RouterNonBlockingCallbackController.class, "router");
32 | }
33 |
34 | /**
35 | * Sample usage: curl "http://localhost:9080/router-non-blocking-callback?minMs=1000&maxMs=2000"
36 | *
37 | * @param minMs
38 | * @param maxMs
39 | * @return
40 | * @throws IOException
41 | */
42 | @RequestMapping("/router-non-blocking-callback")
43 | public DeferredResult nonBlockingRouter(
44 | @RequestParam(value = "minMs", required = false, defaultValue = "0") int minMs,
45 | @RequestParam(value = "maxMs", required = false, defaultValue = "0") int maxMs) throws IOException {
46 |
47 | LOG.logStartNonBlocking();
48 |
49 | DeferredResult deferredResult = new DeferredResult();
50 |
51 | String url = SP_NON_BLOCKING_URL + "?minMs=" + minMs + "&maxMs=" + maxMs;
52 | asyncHttpClient.prepareGet(url).execute(new RouterCallback(LOG, deferredResult));
53 |
54 | LOG.logLeaveThreadNonBlocking();
55 |
56 | // Return to let go of the precious thread we are holding on to...
57 | return deferredResult;
58 | }
59 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/router/nonblocking/lambda/RouterNonBlockingLambdaController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.router.nonblocking.lambda;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.web.bind.annotation.RequestMapping;
6 | import org.springframework.web.bind.annotation.RequestParam;
7 | import org.springframework.web.bind.annotation.RestController;
8 | import org.springframework.web.context.request.async.DeferredResult;
9 | import se.callista.springmvc.asynch.common.lambdasupport.AsyncHttpClientLambdaAware;
10 | import se.callista.springmvc.asynch.common.log.LogHelper;
11 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
12 |
13 | import javax.annotation.PostConstruct;
14 | import java.io.IOException;
15 |
16 | @RestController
17 | public class RouterNonBlockingLambdaController {
18 |
19 | private LogHelper LOG;
20 |
21 | private static final AsyncHttpClientLambdaAware asyncHttpClient = new AsyncHttpClientLambdaAware();
22 |
23 | @Autowired
24 | private LogHelperFactory logFactory;
25 |
26 | @Value("${sp.non_blocking.url}")
27 | private String SP_NON_BLOCKING_URL;
28 |
29 | @PostConstruct
30 | public void initAfterInject() {
31 | LOG = logFactory.getLog(RouterNonBlockingLambdaController.class, "router");
32 | }
33 |
34 | /**
35 | * Sample usage: curl "http://localhost:9080/router-non-blocking-lambda?minMs=1000&maxMs=2000"
36 | *
37 | * @param minMs
38 | * @param maxMs
39 | * @return
40 | * @throws java.io.IOException
41 | */
42 | @RequestMapping("/router-non-blocking-lambda")
43 | public DeferredResult nonBlockingRouter(
44 | @RequestParam(value = "minMs", required = false, defaultValue = "0") int minMs,
45 | @RequestParam(value = "maxMs", required = false, defaultValue = "0") int maxMs) throws IOException {
46 |
47 | LOG.logStartNonBlocking();
48 |
49 | final DeferredResult deferredResult = new DeferredResult<>();
50 |
51 | String url = SP_NON_BLOCKING_URL + "?minMs=" + minMs + "&maxMs=" + maxMs;
52 |
53 | asyncHttpClient.execute(url,
54 | (response) -> {
55 | // TODO: Handle status codes other than 200...
56 | int httpStatus = response.getStatusCode();
57 |
58 | if (deferredResult.isSetOrExpired()) {
59 | LOG.logAlreadyExpiredNonBlocking();
60 |
61 | } else {
62 | boolean deferredStatus = deferredResult.setResult(response.getResponseBody());
63 | LOG.logEndNonBlocking(httpStatus, deferredStatus);
64 | }
65 | return response;
66 | },
67 | (throwable) -> {
68 | // TODO: Handle asynchronous processing errors...
69 |
70 | if (deferredResult.isSetOrExpired()) {
71 | LOG.logExceptionNonBlocking(throwable);
72 | }
73 | }
74 | );
75 |
76 | LOG.logLeaveThreadNonBlocking();
77 |
78 | // Return to let go of the precious thread we are holding on to...
79 | return deferredResult;
80 | }
81 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/router/nonblocking/spring/RouterCallback_Spring_AsyncRestTemplate.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.router.nonblocking.spring;
2 |
3 | import org.springframework.http.ResponseEntity;
4 | import org.springframework.util.concurrent.ListenableFutureCallback;
5 | import org.springframework.web.context.request.async.DeferredResult;
6 | import se.callista.springmvc.asynch.common.log.LogHelper;
7 |
8 | public class RouterCallback_Spring_AsyncRestTemplate implements ListenableFutureCallback> {
9 |
10 | private LogHelper log;
11 | private long reqId;
12 | private DeferredResult deferredResult;
13 |
14 | public RouterCallback_Spring_AsyncRestTemplate(LogHelper log, DeferredResult deferredResult) {
15 | this.log = log;
16 | this.reqId = reqId;
17 | this.deferredResult = deferredResult;
18 | }
19 |
20 | @Override
21 | public void onSuccess(ResponseEntity result) {
22 |
23 | // TODO: Handle status codes other than 200...
24 |
25 | if (deferredResult.isSetOrExpired()) {
26 | log.logAlreadyExpiredNonBlocking();
27 | } else {
28 | boolean deferredStatus = deferredResult.setResult(result.getBody());
29 | log.logEndNonBlocking(result.getStatusCode().value(), deferredStatus);
30 | }
31 |
32 | }
33 |
34 | @Override
35 | public void onFailure(Throwable t) {
36 |
37 | // TODO: Handle asynchronous processing errors...
38 |
39 | if (deferredResult.isSetOrExpired()) {
40 | log.logExceptionNonBlocking(t);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/router/nonblocking/spring/RouterNonBlockingSpringController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.router.nonblocking.spring;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.http.ResponseEntity;
6 | import org.springframework.util.concurrent.ListenableFuture;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestParam;
9 | import org.springframework.web.bind.annotation.RestController;
10 | import org.springframework.web.client.AsyncRestTemplate;
11 | import org.springframework.web.context.request.async.DeferredResult;
12 | import se.callista.springmvc.asynch.common.log.LogHelper;
13 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
14 |
15 | import javax.annotation.PostConstruct;
16 | import java.io.IOException;
17 |
18 | @RestController
19 | public class RouterNonBlockingSpringController {
20 |
21 | private LogHelper LOG;
22 |
23 | private AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
24 |
25 | @Autowired
26 | private LogHelperFactory logFactory;
27 |
28 | @Value("${sp.non_blocking.url}")
29 | private String SP_NON_BLOCKING_URL;
30 |
31 | @PostConstruct
32 | public void initAfterInject() {
33 | LOG = logFactory.getLog(se.callista.springmvc.asynch.pattern.router.nonblocking.callback.RouterNonBlockingCallbackController.class, "router");
34 | }
35 |
36 | /**
37 | * Sample usage: curl "http://localhost:9080/router-non-blocking-spring?minMs=1000&maxMs=2000"
38 | *
39 | * The spring version of asynch http client has two major drawbacks
40 | * 1. It doesn't work with the code below, no call is made to the SP (probably my fault :-)
41 | * 2. The call is not executed non-blocking but instead in a separate thread, i.e. it doesn't scale very good...
42 | *
43 | * Due to the scalability issue it is not used but left as documentation on how it can be used given it is change under the hood to being non-blocking
44 | *
45 | * @param minMs y3
46 | * @param maxMs y3
47 | * @return y3
48 | * @throws java.io.IOException
49 | */
50 | @RequestMapping("/router-non-blocking-spring")
51 | public DeferredResult nonBlockingRouter_Spring(
52 | @RequestParam(value = "minMs", required = false, defaultValue = "0") int minMs,
53 | @RequestParam(value = "maxMs", required = false, defaultValue = "0") int maxMs) throws IOException {
54 |
55 | LOG.logStartNonBlocking();
56 |
57 | DeferredResult deferredResult = new DeferredResult();
58 |
59 | String url = SP_NON_BLOCKING_URL + "?minMs={minMs}&maxMs={maxMs}";
60 | ListenableFuture> futureEntity = asyncRestTemplate.getForEntity(url, String.class, minMs, maxMs);
61 |
62 | // Register a callback for the completion of the asynchronous rest call
63 | futureEntity.addCallback(new RouterCallback_Spring_AsyncRestTemplate(LOG, deferredResult));
64 |
65 | LOG.logLeaveThreadNonBlocking();
66 |
67 | // Return to let go of the precious thread we are holding on to...
68 | return deferredResult;
69 | }
70 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/routingslip/blocking/plain/RoutingSlipBlockingPlainController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.routingslip.blocking.plain;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.http.HttpStatus;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RestController;
9 | import org.springframework.web.client.RestTemplate;
10 | import se.callista.springmvc.asynch.common.log.LogHelper;
11 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
12 |
13 | import javax.annotation.PostConstruct;
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | @RestController
18 | public class RoutingSlipBlockingPlainController {
19 |
20 | private LogHelper LOG;
21 |
22 | private RestTemplate restTemplate = new RestTemplate();
23 |
24 | @Autowired
25 | private LogHelperFactory logFactory;
26 |
27 | @Value("${sp.non_blocking.url}")
28 | private String SP_NON_BLOCKING_URL;
29 |
30 | List resultList = new ArrayList<>();
31 |
32 | @PostConstruct
33 | public void initAfterInject() {
34 | LOG = logFactory.getLog(RoutingSlipBlockingPlainController.class, "routing-slip");
35 | }
36 |
37 | /**
38 | * Sample usage: curl "http://localhost:9080/routing-slip-blocking-plain"
39 | *
40 | * @return
41 | */
42 | @RequestMapping("/routing-slip-blocking-plain")
43 | public String blockingRouter() {
44 |
45 | HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
46 |
47 | LOG.logStartBlocking();
48 |
49 | // Send request #1 and process its result
50 | String result = executeProcessingStep(1);
51 | processResult(result);
52 |
53 | // Send request #2 and process its result
54 | result = executeProcessingStep(2);
55 | processResult(result);
56 |
57 | // Send request #3 and process its result
58 | result = executeProcessingStep(3);
59 | processResult(result);
60 |
61 | // Send request #4 and process its result
62 | result = executeProcessingStep(4);
63 | processResult(result);
64 |
65 | // Send request #5 and process its result
66 | result = executeProcessingStep(5);
67 | processResult(result);
68 |
69 | LOG.logEndBlocking(HttpStatus.OK.value());
70 |
71 | return getTotalResult();
72 | }
73 |
74 | private String executeProcessingStep(int processingStepNo) {
75 | LOG.logStartProcessingStepNonBlocking(processingStepNo);
76 | ResponseEntity result = restTemplate.getForEntity(getUrl(processingStepNo), String.class);
77 | LOG.logEndProcessingStepNonBlocking(processingStepNo, result.getStatusCode().value());
78 | return result.getBody();
79 | }
80 |
81 | private String getUrl(int processingStepNo) {
82 | int sleeptimeMs = 100 * processingStepNo;
83 | return SP_NON_BLOCKING_URL + "?minMs=" + sleeptimeMs + "&maxMs=" + sleeptimeMs;
84 | }
85 |
86 | private void processResult(String result) {
87 | resultList.add(result);
88 | }
89 |
90 | private String getTotalResult() {
91 | String totalResult = "";
92 | for (String r : resultList)
93 | totalResult += r + '\n';
94 | return totalResult;
95 | }
96 |
97 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/routingslip/blocking/statemachine/RoutingSlipBlockingConfiguration.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.routingslip.blocking.statemachine;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.stereotype.Component;
5 | import se.callista.springmvc.asynch.common.statemachine.Processor;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Iterator;
9 | import java.util.List;
10 |
11 | /**
12 | * Created by magnus on 08/06/14.
13 | */
14 | @Component
15 | public class RoutingSlipBlockingConfiguration {
16 |
17 | @Autowired
18 | private SynchProcessor synchProcessor;
19 |
20 | /**
21 | * Simulates setting up a number of processing steps from some kind of configuration...
22 | *
23 | * @return
24 | */
25 | public Iterator getProcessingSteps() {
26 |
27 | List processingSteps = new ArrayList();
28 | for (int i = 0; i < 5; i++) {
29 | processingSteps.add(synchProcessor);
30 | }
31 | return processingSteps.iterator();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/routingslip/blocking/statemachine/RoutingSlipBlockingStateMachineController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.routingslip.blocking.statemachine;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.http.HttpStatus;
5 | import org.springframework.web.bind.annotation.RequestMapping;
6 | import org.springframework.web.bind.annotation.RestController;
7 | import se.callista.springmvc.asynch.common.deferredresult.DeferredResultStateMachineCallback;
8 | import se.callista.springmvc.asynch.common.deferredresult.DeferredResultWithBlockingWait;
9 | import se.callista.springmvc.asynch.common.log.LogHelper;
10 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
11 | import se.callista.springmvc.asynch.common.statemachine.StateMachine;
12 |
13 | import javax.annotation.PostConstruct;
14 |
15 | @RestController
16 | public class RoutingSlipBlockingStateMachineController {
17 |
18 | private LogHelper LOG;
19 |
20 | @Autowired
21 | private RoutingSlipBlockingConfiguration configuration;
22 |
23 | @Autowired
24 | private StateMachine stateMachine;
25 |
26 | @Autowired
27 | private LogHelperFactory logFactory;
28 |
29 | @PostConstruct
30 | public void initAfterInject() {
31 | LOG = logFactory.getLog(RoutingSlipBlockingStateMachineController.class, "routing-slip");
32 | }
33 |
34 | /**
35 | * Sample usage: curl "http://localhost:9080/routing-slip-blocking-state-machine"
36 | *
37 | * @return
38 | */
39 | @RequestMapping("/routing-slip-blocking-state-machine")
40 | public String blockingRoutingSlip() {
41 |
42 | HttpStatus status = HttpStatus.OK;
43 | LOG.logStartBlocking();
44 |
45 | try {
46 | // Create a deferred result that we can wait for in blocking mode
47 | final DeferredResultWithBlockingWait deferredResult = new DeferredResultWithBlockingWait<>();
48 |
49 | // Kick off the processing
50 | stateMachine.initProcessing(configuration.getProcessingSteps(), LOG, new DeferredResultStateMachineCallback(deferredResult));
51 |
52 | // Wait (blocking) for its completion
53 | deferredResult.await();
54 |
55 | // Return the result
56 | return deferredResult.getResult().toString();
57 |
58 | } finally {
59 | LOG.logEndBlocking(status.value());
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/routingslip/blocking/statemachine/SynchProcessor.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.routingslip.blocking.statemachine;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.beans.factory.annotation.Value;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.http.ResponseEntity;
9 | import org.springframework.stereotype.Component;
10 | import org.springframework.web.client.RestTemplate;
11 | import se.callista.springmvc.asynch.common.statemachine.Processor;
12 | import se.callista.springmvc.asynch.common.statemachine.State;
13 | import se.callista.springmvc.asynch.common.statemachine.StateMachine;
14 |
15 | /**
16 | * Created by magnus on 15/05/14.
17 | */
18 | @Component
19 | public class SynchProcessor implements Processor {
20 |
21 | private static final Logger LOG = LoggerFactory.getLogger(SynchProcessor.class);
22 |
23 | @Autowired
24 | private StateMachine stateMachine;
25 |
26 | private RestTemplate restTemplate = new RestTemplate();
27 |
28 | @Value("${sp.non_blocking.url}")
29 | private String SP_NON_BLOCKING_URL;
30 |
31 | @Override
32 | public void process(State state) {
33 |
34 | int sleeptimeMs = 100 * (state.getProcessingStepNo());
35 | String url = SP_NON_BLOCKING_URL + "?minMs=" + sleeptimeMs + "&maxMs=" + sleeptimeMs;
36 |
37 | LOG.debug("Launch synch call");
38 |
39 | ResponseEntity result = restTemplate.getForEntity(
40 | SP_NON_BLOCKING_URL + "?minMs={minMs}&maxMs={maxMs}", String.class, sleeptimeMs, sleeptimeMs);
41 |
42 | // TODO: Handle status codes other than 200...
43 | HttpStatus status = result.getStatusCode();
44 |
45 | LOG.debug("Synch call complete, hand over to the state machine for the next action");
46 | state.appendResult(result.getBody() + '\n');
47 | stateMachine.executeNextStep(state);
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/routingslip/nonblocking/lambda/RoutingSlipNonBlockingLambdaController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.routingslip.nonblocking.lambda;
2 |
3 | import com.ning.http.client.ListenableFuture;
4 | import com.ning.http.client.Response;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.beans.factory.annotation.Value;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RestController;
9 | import org.springframework.web.context.request.async.DeferredResult;
10 | import se.callista.springmvc.asynch.common.lambdasupport.AsyncHttpClientLambdaAware;
11 | import se.callista.springmvc.asynch.common.log.LogHelper;
12 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
13 |
14 | import javax.annotation.PostConstruct;
15 | import java.io.IOException;
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | @RestController
20 | public class RoutingSlipNonBlockingLambdaController {
21 |
22 | private class Result {
23 | List resultList = new ArrayList<>();
24 |
25 | public void processResult(String result) {
26 | resultList.add(result);
27 | }
28 |
29 | public String getTotalResult() {
30 | String totalResult = "";
31 | for (String r : resultList)
32 | totalResult += r + '\n';
33 | return totalResult;
34 | }
35 | }
36 |
37 | private LogHelper LOG;
38 |
39 | private static final AsyncHttpClientLambdaAware asyncHttpClient = new AsyncHttpClientLambdaAware();
40 |
41 | @Autowired
42 | private LogHelperFactory logFactory;
43 |
44 | @Value("${sp.non_blocking.url}")
45 | private String SP_NON_BLOCKING_URL;
46 |
47 | @PostConstruct
48 | public void initAfterInject() {
49 | LOG = logFactory.getLog(se.callista.springmvc.asynch.pattern.routingslip.nonblocking.lambda.RoutingSlipNonBlockingLambdaController.class, "routing-slip");
50 | }
51 |
52 | /**
53 | * Sample usage: curl "http://localhost:9181/routing-slip-non-blocking-lambds"
54 | *
55 | * @return
56 | */
57 | @RequestMapping("/routing-slip-non-blocking-lambda")
58 | public DeferredResult nonBlockingRoutingSlip() throws IOException {
59 |
60 | LOG.logStartNonBlocking();
61 |
62 | DeferredResult deferredResult = new DeferredResult<>();
63 | Result r = new Result();
64 |
65 | // Kick off the asynch processing of five sequentially executed asynch processing steps
66 |
67 | // Send request #1
68 | LOG.logStartProcessingStepNonBlocking(1);
69 | ListenableFuture execute = asyncHttpClient.execute(getUrl(1),
70 | (Response r1) -> {
71 | LOG.logEndProcessingStepNonBlocking(1, r1.getStatusCode());
72 |
73 | // Process response #1
74 | r.processResult(r1.getResponseBody());
75 |
76 | // Send request #2
77 | LOG.logStartProcessingStepNonBlocking(2);
78 | asyncHttpClient.execute(getUrl(2),
79 | (Response r2) -> {
80 | LOG.logEndProcessingStepNonBlocking(2, r2.getStatusCode());
81 |
82 | // Process response #2
83 | r.processResult(r2.getResponseBody());
84 |
85 | // Send request #3
86 | LOG.logStartProcessingStepNonBlocking(3);
87 | asyncHttpClient.execute(getUrl(3),
88 | (Response r3) -> {
89 | LOG.logEndProcessingStepNonBlocking(3, r3.getStatusCode());
90 |
91 | // Process response #3
92 | r.processResult(r3.getResponseBody());
93 |
94 | // Send request #4
95 | LOG.logStartProcessingStepNonBlocking(4);
96 | asyncHttpClient.execute(getUrl(4),
97 | (Response r4) -> {
98 | LOG.logEndProcessingStepNonBlocking(4, r4.getStatusCode());
99 |
100 | // Process response #4
101 | r.processResult(r4.getResponseBody());
102 |
103 | // Send request #5
104 | LOG.logStartProcessingStepNonBlocking(5);
105 | asyncHttpClient.execute(getUrl(5),
106 | (Response r5) -> {
107 | LOG.logEndProcessingStepNonBlocking(5, r5.getStatusCode());
108 |
109 | // Process response #5
110 | r.processResult(r5.getResponseBody());
111 |
112 | // Get the total result and set it on the deferred result
113 | boolean deferredStatus = deferredResult.setResult(r.getTotalResult());
114 | LOG.logEndNonBlocking(r5.getStatusCode(), deferredStatus);
115 |
116 | return r5;
117 | });
118 | return r4;
119 | });
120 | return r3;
121 | });
122 | return r2;
123 | });
124 | return r1;
125 | });
126 |
127 | LOG.logLeaveThreadNonBlocking();
128 |
129 | // Return to let go of the precious thread we are holding on to...
130 | return deferredResult;
131 | }
132 |
133 | private String getUrl(int processingStepNo) {
134 | int sleeptimeMs = 100 * processingStepNo;
135 | return SP_NON_BLOCKING_URL + "?minMs=" + sleeptimeMs + "&maxMs=" + sleeptimeMs;
136 | }
137 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/routingslip/nonblocking/statemachine/AsynchProcessor.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.routingslip.nonblocking.statemachine;
2 |
3 | import com.ning.http.client.AsyncHttpClient;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.stereotype.Component;
9 | import se.callista.springmvc.asynch.common.statemachine.Processor;
10 | import se.callista.springmvc.asynch.common.statemachine.State;
11 | import se.callista.springmvc.asynch.common.statemachine.StateMachine;
12 |
13 | import java.io.IOException;
14 |
15 | /**
16 | * Created by magnus on 15/05/14.
17 | */
18 | @Component
19 | public class AsynchProcessor implements Processor {
20 |
21 | private static final Logger LOG = LoggerFactory.getLogger(AsynchProcessor.class);
22 |
23 | @Autowired
24 | private StateMachine stateMachine;
25 |
26 | private static final AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
27 |
28 | @Value("${sp.non_blocking.url}")
29 | private String SP_NON_BLOCKING_URL;
30 |
31 | @Override
32 | public void process(State state) {
33 |
34 | try {
35 | int sleeptimeMs = 100 * (state.getProcessingStepNo());
36 | String url = SP_NON_BLOCKING_URL + "?minMs=" + sleeptimeMs + "&maxMs=" + sleeptimeMs;
37 |
38 | LOG.debug("Launch asynch call");
39 | asyncHttpClient.prepareGet(url).execute(new AsynchProcessorCallback(stateMachine, state));
40 |
41 | } catch (IOException e) {
42 | throw new RuntimeException(e);
43 | }
44 |
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/routingslip/nonblocking/statemachine/AsynchProcessorCallback.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.routingslip.nonblocking.statemachine;
2 |
3 | import com.ning.http.client.AsyncCompletionHandler;
4 | import com.ning.http.client.Response;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import se.callista.springmvc.asynch.common.statemachine.State;
8 | import se.callista.springmvc.asynch.common.statemachine.StateMachine;
9 |
10 | public class AsynchProcessorCallback extends AsyncCompletionHandler {
11 |
12 | private static final Logger LOG = LoggerFactory.getLogger(AsynchProcessorCallback.class);
13 |
14 | private final StateMachine stateMachine;
15 | private final State state;
16 |
17 | // private long reqId;
18 | // private AtomicLong concurrentRequests;
19 | // private DeferredResult deferredResult;
20 |
21 | public AsynchProcessorCallback(StateMachine stateMachine, State state) {
22 | this.stateMachine = stateMachine;
23 | this.state = state;
24 | }
25 |
26 | @Override
27 | public Response onCompleted(Response response) throws Exception{
28 |
29 | // TODO: Handle status codes other than 200...
30 | // int httpStatus = response.getStatusCode();
31 |
32 | state.getLog().logAsynchProcessingStepComplete();
33 | state.appendResult(response.getResponseBody() + '\n');
34 | stateMachine.executeNextStep(state);
35 |
36 | return response;
37 | }
38 |
39 | @Override
40 | public void onThrowable(Throwable t){
41 |
42 | // TODO: Handle asynchronous processing errors...
43 | state.getLog().logExceptionNonBlocking(t);
44 | }
45 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/routingslip/nonblocking/statemachine/RoutingSlipNonBlockingConfiguration.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.routingslip.nonblocking.statemachine;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.stereotype.Component;
5 | import se.callista.springmvc.asynch.common.statemachine.Processor;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Iterator;
9 | import java.util.List;
10 |
11 | /**
12 | * Created by magnus on 08/06/14.
13 | */
14 | @Component
15 | public class RoutingSlipNonBlockingConfiguration {
16 |
17 | @Autowired
18 | private AsynchProcessor asynchProcessor;
19 |
20 | /**
21 | * Simulates setting up a number of processing steps from some kind of configuration...
22 | *
23 | * @return
24 | */
25 | public Iterator getProcessingSteps() {
26 |
27 | List processingSteps = new ArrayList();
28 | for (int i = 0; i < 5; i++) {
29 | processingSteps.add(asynchProcessor);
30 | }
31 | return processingSteps.iterator();
32 | }
33 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/java/se/callista/springmvc/asynch/pattern/routingslip/nonblocking/statemachine/RoutingSlipNonBlockingStateMachineController.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.routingslip.nonblocking.statemachine;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.web.bind.annotation.RequestMapping;
5 | import org.springframework.web.bind.annotation.RestController;
6 | import org.springframework.web.context.request.async.DeferredResult;
7 | import se.callista.springmvc.asynch.common.deferredresult.DeferredResultStateMachineCallback;
8 | import se.callista.springmvc.asynch.common.log.LogHelper;
9 | import se.callista.springmvc.asynch.common.log.LogHelperFactory;
10 | import se.callista.springmvc.asynch.common.statemachine.Processor;
11 | import se.callista.springmvc.asynch.common.statemachine.StateMachine;
12 |
13 | import javax.annotation.PostConstruct;
14 | import java.io.IOException;
15 | import java.util.Iterator;
16 |
17 | @RestController
18 | public class RoutingSlipNonBlockingStateMachineController {
19 |
20 | private LogHelper LOG;
21 |
22 | @Autowired
23 | private RoutingSlipNonBlockingConfiguration configuration;
24 |
25 | @Autowired
26 | private StateMachine stateMachine;
27 |
28 | @Autowired
29 | private LogHelperFactory logFactory;
30 |
31 | @PostConstruct
32 | public void initAfterInject() {
33 | LOG = logFactory.getLog(RoutingSlipNonBlockingStateMachineController.class, "routing-slip");
34 | }
35 |
36 | /**
37 | * Sample usage: curl "http://localhost:9080/routing-slip-non-blocking-state-machine"
38 | *
39 | * @return
40 | */
41 | @RequestMapping("/routing-slip-non-blocking-state-machine")
42 | public DeferredResult nonBlockingRoutingSlip() throws IOException {
43 |
44 | LOG.logStartNonBlocking();
45 |
46 | // Create a deferred result
47 | final DeferredResult deferredResult = new DeferredResult<>();
48 |
49 | // Kick off the asynch processing of a number of sequentially executed asynch processing steps
50 | Iterator processingSteps = configuration.getProcessingSteps();
51 |
52 | stateMachine.initProcessing(processingSteps, LOG, new DeferredResultStateMachineCallback(deferredResult));
53 |
54 | LOG.logLeaveThreadNonBlocking();
55 |
56 | // Return to let go of the precious thread we are holding on to...
57 | return deferredResult;
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=9080
2 |
3 |
4 | #THE DEFAULT VALUE FOT THREAD POOL SIZE IN TOMCAT
5 | servlet.container.maxThreads=200
6 | #servlet.container.maxThreads=50 FOR NON-BLOCKING TESTS
7 | #servlet.container.maxThreads=500 FOR BLOCKING TESTS
8 |
9 | threadPool.db.init_size=5
10 | threadPool.db.max_size=50
11 | threadPool.db.queue_size=1000000
12 |
13 | #sp.blocking.url=http://localhost:9090/process-blocking
14 | #sp.non_blocking.url=http://localhost:${server.port}/process-non-blocking
15 | sp.server.port=9090
16 | sp.non_blocking.url=http://localhost:${sp.server.port}/process-non-blocking
17 |
18 | aggregator.timeoutMs=3000
19 |
20 | statistics.requestsPerLog=5000
21 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | System.out
13 |
14 | %d{yyyy-MM-dd HH:mm:ss:SSS} %-5p %t %c{1}:%L - %m%n
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/test/java/se/callista/springmvc/asynch/common/AsynchTestBase.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import se.callista.springmvc.asynch.common.embeddedhttpserver.EmbeddedHttpServerTestBase;
6 |
7 | import javax.servlet.http.HttpServletRequest;
8 | import javax.servlet.http.HttpServletResponse;
9 | import java.io.IOException;
10 | import java.net.URISyntaxException;
11 | import java.util.HashMap;
12 | import java.util.Map;
13 |
14 | import static javax.servlet.http.HttpServletResponse.SC_OK;
15 | import static org.apache.commons.io.IOUtils.write;
16 |
17 | /**
18 | * Created by magnus on 22/07/14.
19 | */
20 | abstract public class AsynchTestBase extends EmbeddedHttpServerTestBase {
21 |
22 | private static final Logger LOG = LoggerFactory.getLogger(AsynchTestBase.class);
23 |
24 | protected void createResponse(HttpServletRequest request, String requestBody, HttpServletResponse response) throws IOException, URISyntaxException {
25 |
26 | Map parMap = new HashMap<>();
27 | parMap.put("minMs", "0");
28 | parMap.put("maxMs", "0");
29 | getQueryParameters(request, parMap);
30 |
31 | int minMs = Integer.parseInt(parMap.get("minMs"));
32 | int maxMs = Integer.parseInt(parMap.get("maxMs"));
33 | int processingTimeMs = calculateProcessingTime(minMs, maxMs);
34 |
35 | LOG.debug("Start blocking request, processing time: {} ms (" + minMs + " - " + maxMs + ").", processingTimeMs);
36 | try {
37 | Thread.sleep(processingTimeMs);
38 | }
39 | catch (InterruptedException e) {}
40 |
41 | String responseBody = "{\"status\":\"Ok\",\"processingTimeMs\":" + processingTimeMs + "}";
42 | response.setStatus(SC_OK);
43 | response.setContentType("text/plain;charset=ISO-8859-1");
44 | write(responseBody, response.getOutputStream());
45 | }
46 |
47 | private int calculateProcessingTime(int minMs, int maxMs) {
48 | if (maxMs < minMs) maxMs = minMs;
49 | int processingTimeMs = minMs + (int) (Math.random() * (maxMs - minMs));
50 | return processingTimeMs;
51 | }
52 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/test/java/se/callista/springmvc/asynch/common/ProcessingStatus.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common;
2 |
3 | import javax.xml.bind.annotation.XmlElement;
4 | import javax.xml.bind.annotation.XmlRootElement;
5 |
6 | @XmlRootElement
7 | public class ProcessingStatus {
8 |
9 | @XmlElement
10 | private final String status;
11 |
12 | @XmlElement
13 | private final int processingTimeMs;
14 |
15 | public ProcessingStatus() {
16 | status = "UNKNOWN";
17 | processingTimeMs = -1;
18 | }
19 |
20 | public ProcessingStatus(String status, int processingTimeMs) {
21 | this.status = status;
22 | this.processingTimeMs = processingTimeMs;
23 | }
24 |
25 | public String getStatus() {
26 | return status;
27 | }
28 |
29 | public int getProcessingTimeMs() {
30 | return processingTimeMs;
31 | }
32 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/test/java/se/callista/springmvc/asynch/common/embeddedhttpserver/EmbeddedHttpServer.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.embeddedhttpserver;
2 |
3 | import org.eclipse.jetty.server.Request;
4 | import org.eclipse.jetty.server.Server;
5 | import org.eclipse.jetty.server.handler.AbstractHandler;
6 |
7 | import javax.servlet.ServletException;
8 | import javax.servlet.http.HttpServletRequest;
9 | import javax.servlet.http.HttpServletResponse;
10 | import java.io.IOException;
11 |
12 | /**
13 | * Created by magnus on 22/07/14.
14 | */
15 | public class EmbeddedHttpServer {
16 |
17 | private int port;
18 | private Server server;
19 | private RequestHandler requestHandler;
20 |
21 | public EmbeddedHttpServer(int port, RequestHandler requestHandler) {
22 | this.port = port;
23 | this.requestHandler = requestHandler;
24 | }
25 |
26 | public void start() throws Exception {
27 | configureServer();
28 | server.start();
29 | }
30 |
31 | public void stop() throws Exception {
32 | server.stop();
33 | }
34 |
35 | protected void configureServer() {
36 | server = new Server(port);
37 | server.setHandler(new AbstractHandler() {
38 | @Override
39 | public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
40 | try {
41 | requestHandler.handle(target, baseRequest, request, response);
42 | } catch (ServletException e) {
43 | throw e;
44 | } catch (IOException e) {
45 | throw e;
46 | } catch (Exception e) {
47 | throw new RuntimeException(e);
48 | }
49 | }
50 | });
51 | /*
52 | server.setHandler(new AbstractHandler() {
53 | public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException {
54 | Request baseRequest = request instanceof Request ? (Request) request : HttpConnection.getCurrentConnection().getRequest();
55 | setResponseBody(getMockResponseData());
56 | setRequestBody(IOUtils.toString(baseRequest.getInputStream()));
57 | response.setStatus(SC_OK);
58 | response.setContentType("text/xml;charset=utf-8");
59 | write(getResponseBody(), response.getOutputStream());
60 | baseRequest.setHandled(true);
61 | }
62 | });
63 | */
64 | }
65 |
66 | protected Server getServer() {
67 | return server;
68 | }
69 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/test/java/se/callista/springmvc/asynch/common/embeddedhttpserver/EmbeddedHttpServerTestBase.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.embeddedhttpserver;
2 |
3 | import org.apache.commons.io.IOUtils;
4 | import org.apache.http.NameValuePair;
5 | import org.apache.http.client.utils.URLEncodedUtils;
6 | import org.junit.After;
7 | import org.junit.Before;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.beans.factory.annotation.Value;
11 |
12 | import javax.servlet.http.HttpServletRequest;
13 | import javax.servlet.http.HttpServletResponse;
14 | import java.io.IOException;
15 | import java.net.URI;
16 | import java.net.URISyntaxException;
17 | import java.util.List;
18 | import java.util.Map;
19 |
20 | /**
21 | * Created by magnus on 22/07/14.
22 | */
23 | abstract public class EmbeddedHttpServerTestBase {
24 |
25 | private static final Logger LOG = LoggerFactory.getLogger(EmbeddedHttpServerTestBase.class);
26 |
27 | private EmbeddedHttpServer server;
28 |
29 | @Value("${sp.server.port}")
30 | private int port;
31 |
32 | @Before
33 | public void startTestServer() throws Exception {
34 | LOG.debug("Start embedded Jetty-server on port: " + port);
35 | server = new EmbeddedHttpServer(port, (target, baseRequest, request, response) -> {
36 | String requestBody = IOUtils.toString(baseRequest.getInputStream());
37 | createResponse(request, requestBody, response);
38 | baseRequest.setHandled(true);
39 | });
40 | server.start();
41 | }
42 |
43 | @After
44 | public void stopTestServer() throws Exception {
45 | server.stop();
46 | }
47 |
48 | abstract protected void createResponse(HttpServletRequest request, String requestBody, HttpServletResponse response) throws IOException, URISyntaxException;
49 |
50 | protected void getQueryParameters(HttpServletRequest request, Map parMap) throws URISyntaxException {
51 | List params = URLEncodedUtils.parse(new URI(request.getRequestURL().toString() + "?" + request.getQueryString()), "UTF-8");
52 |
53 | for (NameValuePair param : params) {
54 | parMap.put(param.getName(), param.getValue());
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/test/java/se/callista/springmvc/asynch/common/embeddedhttpserver/RequestHandler.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.common.embeddedhttpserver;
2 |
3 | import org.eclipse.jetty.server.Request;
4 |
5 | import javax.servlet.ServletException;
6 | import javax.servlet.http.HttpServletRequest;
7 | import javax.servlet.http.HttpServletResponse;
8 | import java.io.IOException;
9 |
10 | /**
11 | * Created by magnus on 22/07/14.
12 | */
13 | public interface RequestHandler {
14 |
15 | /** Handle a request.
16 | * @param target The target of the request - either a URI or a name.
17 | * @param baseRequest The original unwrapped request object.
18 | * @param request The request either as the {@link Request}
19 | * object or a wrapper of that request. The {@link org.eclipse.jetty.server.AbstractHttpConnection#getCurrentConnection()}
20 | * method can be used access the Request object if required.
21 | * @param response The response as the {@link org.eclipse.jetty.server.Response}
22 | * object or a wrapper of that request. The {@link org.eclipse.jetty.server.AbstractHttpConnection#getCurrentConnection()}
23 | * method can be used access the Response object if required.
24 | * @throws IOException
25 | * @throws ServletException
26 | */
27 | public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws Exception;
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/test/java/se/callista/springmvc/asynch/pattern/aggregator/AggregatorControllerTest.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.aggregator;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.mockito.MockitoAnnotations;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.beans.factory.annotation.Value;
10 | import org.springframework.boot.test.SpringApplicationConfiguration;
11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12 | import org.springframework.test.context.web.WebAppConfiguration;
13 | import org.springframework.test.web.servlet.MockMvc;
14 | import org.springframework.test.web.servlet.MvcResult;
15 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
16 | import org.springframework.web.context.WebApplicationContext;
17 | import se.callista.springmvc.asynch.Application;
18 | import se.callista.springmvc.asynch.common.AsynchTestBase;
19 | import se.callista.springmvc.asynch.common.ProcessingStatus;
20 |
21 | import static org.junit.Assert.assertTrue;
22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
23 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
24 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
25 |
26 | /**
27 | * Created by magnus on 29/05/14.
28 | */
29 | @RunWith(SpringJUnit4ClassRunner.class)
30 | @SpringApplicationConfiguration(classes = Application.class)
31 | @WebAppConfiguration
32 | public class AggregatorControllerTest extends AsynchTestBase {
33 |
34 | private MockMvc mockMvc;
35 |
36 | @Autowired
37 | WebApplicationContext wac;
38 |
39 | @Value("${aggregator.timeoutMs}")
40 | private int TIMEOUT_MS;
41 |
42 | private final String expectedResultSingle = "{\"status\":\"Ok\",\"processingTimeMs\":2000}";
43 | private final String expectedResult =
44 | expectedResultSingle + '\n' +
45 | expectedResultSingle + '\n' +
46 | expectedResultSingle + '\n';
47 |
48 | @Before
49 | public void setup(){
50 |
51 | // Process mock annotations
52 | MockitoAnnotations.initMocks(this);
53 |
54 | // Setup Spring test in webapp-mode (same config as spring-boot)
55 | this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
56 |
57 | }
58 |
59 | @Test
60 | public void testAggregatorBlocking() throws Exception{
61 | this.mockMvc.perform(get("/aggregate-blocking?minMs=2000&maxMs=2000"))
62 | .andExpect(status().isOk())
63 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
64 | .andExpect(content().string(expectedResult));
65 | }
66 |
67 | @Test
68 | public void testAggregatorNonBlockingCallback() throws Exception {
69 |
70 | MvcResult mvcResult = this.mockMvc.perform(get("/aggregate-non-blocking-callback?minMs=2000&maxMs=2000"))
71 | .andExpect(request().asyncStarted())
72 | .andReturn();
73 |
74 | mvcResult.getAsyncResult();
75 |
76 | this.mockMvc.perform(asyncDispatch(mvcResult))
77 | .andExpect(status().isOk())
78 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
79 | .andExpect(content().string(expectedResult));
80 | }
81 |
82 | @Test
83 | public void testAggregatorNonBlockingCallbackTimeout() throws Exception {
84 |
85 | int minMs = (TIMEOUT_MS < 1000) ? 0 : TIMEOUT_MS - 1000;
86 | int maxMs = TIMEOUT_MS + 1000;
87 | int dbHits = 10;
88 |
89 | MvcResult mvcResult = this.mockMvc.perform(get("/aggregate-non-blocking-callback?dbHits=" + dbHits + "&minMs=" + minMs + "&maxMs=" + maxMs))
90 | .andExpect(request().asyncStarted())
91 | .andReturn();
92 |
93 | mvcResult.getAsyncResult();
94 |
95 | this.mockMvc.perform(asyncDispatch(mvcResult))
96 | .andExpect(status().isOk())
97 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"));
98 |
99 | String result = mvcResult.getAsyncResult().toString();
100 |
101 | System.err.println("JSON: " + result);
102 | String[] psArr = result.split("\n");
103 |
104 | // Verify that we got some timeouts
105 | assertTrue("Expected at least one timeout to occur", psArr.length < dbHits);
106 |
107 | System.err.println("assert that no response time was over the timeout: " + TIMEOUT_MS);
108 | ObjectMapper mapper = new ObjectMapper();
109 | for (int i = 0; i < psArr.length; i++) {
110 | ProcessingStatus ps = mapper.readValue(psArr[i], ProcessingStatus.class);
111 | System.err.println("psArr: " + ps.getStatus() + " - " + ps.getProcessingTimeMs());
112 | assertTrue(ps.getProcessingTimeMs() < TIMEOUT_MS);
113 | }
114 | }
115 |
116 | @Test
117 | public void testAggregatorNonBlockingLambda() throws Exception {
118 |
119 | MvcResult mvcResult = this.mockMvc.perform(get("/aggregate-non-blocking-lambda?minMs=2000&maxMs=2000"))
120 | .andExpect(request().asyncStarted())
121 | .andReturn();
122 |
123 | mvcResult.getAsyncResult();
124 |
125 | this.mockMvc.perform(asyncDispatch(mvcResult))
126 | .andExpect(status().isOk())
127 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
128 | .andExpect(content().string(expectedResult));
129 | }
130 |
131 | @Test
132 | public void testAggregatorNonBlockingLambdaTimeout() throws Exception {
133 |
134 | int minMs = (TIMEOUT_MS < 1000) ? 0 : TIMEOUT_MS - 1000;
135 | int maxMs = TIMEOUT_MS + 1000;
136 | int dbHits = 10;
137 |
138 | MvcResult mvcResult = this.mockMvc.perform(get("/aggregate-non-blocking-lambda?dbHits=" + dbHits + "&minMs=" + minMs + "&maxMs=" + maxMs))
139 | .andExpect(request().asyncStarted())
140 | .andReturn();
141 |
142 | mvcResult.getAsyncResult();
143 |
144 | this.mockMvc.perform(asyncDispatch(mvcResult))
145 | .andExpect(status().isOk())
146 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"));
147 |
148 | String result = mvcResult.getAsyncResult().toString();
149 |
150 | System.err.println("JSON: " + result);
151 | String[] psArr = result.split("\n");
152 |
153 | // Verify that we got some timeouts
154 | assertTrue("Expected at least one timeout to occur", psArr.length < dbHits);
155 |
156 | System.err.println("assert that no response time was over the timeout: " + TIMEOUT_MS);
157 | ObjectMapper mapper = new ObjectMapper();
158 | for (int i = 0; i < psArr.length; i++) {
159 | ProcessingStatus ps = mapper.readValue(psArr[i], ProcessingStatus.class);
160 | System.err.println("psArr: " + ps.getStatus() + " - " + ps.getProcessingTimeMs());
161 | assertTrue(ps.getProcessingTimeMs() < TIMEOUT_MS);
162 | }
163 | }
164 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/test/java/se/callista/springmvc/asynch/pattern/router/RouterControllerTest.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.router;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.mockito.MockitoAnnotations;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.boot.test.SpringApplicationConfiguration;
9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10 | import org.springframework.test.context.web.WebAppConfiguration;
11 | import org.springframework.test.web.servlet.MockMvc;
12 | import org.springframework.test.web.servlet.MvcResult;
13 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
14 | import org.springframework.web.context.WebApplicationContext;
15 | import se.callista.springmvc.asynch.Application;
16 | import se.callista.springmvc.asynch.common.AsynchTestBase;
17 |
18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
19 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
21 |
22 |
23 | /**
24 | * Created by magnus on 29/05/14.
25 | */
26 | @RunWith(SpringJUnit4ClassRunner.class)
27 | @SpringApplicationConfiguration(classes = Application.class)
28 | @WebAppConfiguration
29 | public class RouterControllerTest extends AsynchTestBase {
30 |
31 | private MockMvc mockMvc;
32 |
33 | @Autowired
34 | WebApplicationContext wac;
35 |
36 | private final String expectedResult = "{\"status\":\"Ok\",\"processingTimeMs\":2000}";
37 |
38 | @Before
39 | public void setup(){
40 |
41 | // Process mock annotations
42 | MockitoAnnotations.initMocks(this);
43 |
44 | // Setup Spring test in webapp-mode (same config as spring-boot)
45 | this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
46 | }
47 |
48 | @Test
49 | public void testRouterBlocking() throws Exception{
50 | String url = "/router-blocking?minMs=2000&maxMs=2000";
51 | this.mockMvc.perform(get(url))
52 | .andExpect(status().isOk())
53 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
54 | .andExpect(content().string(expectedResult));
55 | }
56 |
57 | @Test
58 | public void testRouterNonBlockingCallback() throws Exception {
59 |
60 | String url = "/router-non-blocking-callback?minMs=2000&maxMs=2000";
61 | testNonBlocking(url);
62 | }
63 |
64 | @Test
65 | public void testRouterNonBlockingAnonymous() throws Exception {
66 |
67 | String url = "/router-non-blocking-anonymous?minMs=2000&maxMs=2000";
68 | testNonBlocking(url);
69 | }
70 |
71 | @Test
72 | public void testRouterNonBlockingLambda() throws Exception {
73 |
74 | String url = "/router-non-blocking-lambda?minMs=2000&maxMs=2000";
75 | testNonBlocking(url);
76 | }
77 |
78 | @Test
79 | public void testRouterNonBlockingSpring() throws Exception {
80 |
81 | // NOTE: Today's implementation in Spring of the AsyncRestTemplate is thread-blocking and therefore doesn't scale
82 | String url = "/router-non-blocking-anonymous?minMs=2000&maxMs=2000";
83 | testNonBlocking(url);
84 | }
85 |
86 | private void testNonBlocking(String url) throws Exception {
87 | MvcResult mvcResult = this.mockMvc.perform(get(url))
88 | .andExpect(request().asyncStarted())
89 | .andReturn();
90 |
91 | mvcResult.getAsyncResult();
92 |
93 | this.mockMvc.perform(asyncDispatch(mvcResult))
94 | .andExpect(status().isOk())
95 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
96 | .andExpect(content().string(expectedResult));
97 | }
98 |
99 |
100 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/src/test/java/se/callista/springmvc/asynch/pattern/routingslip/RoutingSlipControllerTest.java:
--------------------------------------------------------------------------------
1 | package se.callista.springmvc.asynch.pattern.routingslip;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.mockito.MockitoAnnotations;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.boot.test.SpringApplicationConfiguration;
9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10 | import org.springframework.test.context.web.WebAppConfiguration;
11 | import org.springframework.test.web.servlet.MockMvc;
12 | import org.springframework.test.web.servlet.MvcResult;
13 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
14 | import org.springframework.web.context.WebApplicationContext;
15 | import se.callista.springmvc.asynch.Application;
16 | import se.callista.springmvc.asynch.common.AsynchTestBase;
17 |
18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
19 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
21 |
22 |
23 | /**
24 | * Created by magnus on 29/05/14.
25 | */
26 | @RunWith(SpringJUnit4ClassRunner.class)
27 | @SpringApplicationConfiguration(classes = Application.class)
28 | @WebAppConfiguration
29 | public class RoutingSlipControllerTest extends AsynchTestBase {
30 |
31 | private MockMvc mockMvc;
32 |
33 | @Autowired
34 | WebApplicationContext wac;
35 |
36 | private final String expectedResultSingle = "{\"status\":\"Ok\",\"processingTimeMs\":2000}";
37 | private final String expectedResult =
38 | "{\"status\":\"Ok\",\"processingTimeMs\":100}\n" +
39 | "{\"status\":\"Ok\",\"processingTimeMs\":200}\n" +
40 | "{\"status\":\"Ok\",\"processingTimeMs\":300}\n" +
41 | "{\"status\":\"Ok\",\"processingTimeMs\":400}\n" +
42 | "{\"status\":\"Ok\",\"processingTimeMs\":500}\n";
43 |
44 | @Before
45 | public void setup(){
46 |
47 | // Process mock annotations
48 | MockitoAnnotations.initMocks(this);
49 |
50 | // Setup Spring test in webapp-mode (same config as spring-boot)
51 | this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
52 | }
53 |
54 | @Test
55 | public void testRoutingSlipBlockingStateMachine() throws Exception{
56 | this.mockMvc.perform(get("/routing-slip-blocking-state-machine"))
57 | .andExpect(status().isOk())
58 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
59 | .andExpect(content().string(expectedResult));
60 | }
61 |
62 | @Test
63 | public void testRoutingSlipBlockingPlain() throws Exception{
64 | this.mockMvc.perform(get("/routing-slip-blocking-plain"))
65 | .andExpect(status().isOk())
66 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
67 | .andExpect(content().string(expectedResult));
68 | }
69 |
70 | @Test
71 | public void testRoutingSlipNonBlockingStateMachine() throws Exception {
72 |
73 | MvcResult mvcResult = this.mockMvc.perform(get("/routing-slip-non-blocking-state-machine"))
74 | .andExpect(request().asyncStarted())
75 | // .andExpect(request().asyncResult(expectedResult))
76 | .andReturn();
77 |
78 | mvcResult.getAsyncResult();
79 |
80 | this.mockMvc.perform(asyncDispatch(mvcResult))
81 | .andExpect(status().isOk())
82 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
83 | .andExpect(content().string(expectedResult));
84 | }
85 |
86 | @Test
87 | public void testRoutingSlipNonBlockingLambda() throws Exception {
88 |
89 | MvcResult mvcResult = this.mockMvc.perform(get("/routing-slip-non-blocking-lambda"))
90 | .andExpect(request().asyncStarted())
91 | // .andExpect(request().asyncResult(expectedResult))
92 | .andReturn();
93 |
94 | mvcResult.getAsyncResult();
95 |
96 | this.mockMvc.perform(asyncDispatch(mvcResult))
97 | .andExpect(status().isOk())
98 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
99 | .andExpect(content().string(expectedResult));
100 | }
101 |
102 | /**
103 | * Same test as testRoutingSlipNonBlockingLambda that ensures that no state is kept between calls to the routing-slip-non-blocking-lambda service
104 | *
105 | * @throws Exception
106 | */
107 | @Test
108 | public void testRoutingSlipNonBlockingLambda2() throws Exception {
109 |
110 | MvcResult mvcResult = this.mockMvc.perform(get("/routing-slip-non-blocking-lambda"))
111 | .andExpect(request().asyncStarted())
112 | // .andExpect(request().asyncResult(expectedResult))
113 | .andReturn();
114 |
115 | mvcResult.getAsyncResult();
116 |
117 | this.mockMvc.perform(asyncDispatch(mvcResult))
118 | .andExpect(status().isOk())
119 | .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
120 | .andExpect(content().string(expectedResult));
121 | }
122 | }
--------------------------------------------------------------------------------
/spring-mvc-asynch/todo.txt:
--------------------------------------------------------------------------------
1 | -> routing slip, två impl till: En med cascading annonyma interface impl (+ ev Javav 8 Labmdas) + en med en massa callbacks + en för sekventiellt...
2 |
3 | - testa av java 8 stöd i IntelliJ, skal man köra gradle idea tasket eller räcker det at importera projektetn igen?
4 |
5 | - autostart av teststubbe
--------------------------------------------------------------------------------