Codebase list trapperkeeper-webserver-jetty9-clojure / 5630d27
New upstream version 1.7.0 Apollon Oikonomopoulos 6 years ago
100 changed file(s) with 11133 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 .lein-failures
1 .lein-repl-history
2 target/
3 checkouts/
4 pom.xml
5 .nrepl-port
6 /resources/locales.clj
7 /resources/puppetlabs/trapperkeeper_webserver_jetty9/Messages*.class
0 language: clojure
1 lein: 2.7.1
2 jdk:
3 - oraclejdk7
4 - openjdk7
5 script: ./ext/travisci/test.sh
6 notifications:
7 email: false
8
9 # workaround for buffer overflow issue, ref https://github.com/travis-ci/travis-ci/issues/5227
10 addons:
11 hosts:
12 - myshorthost
13 hostname: myshorthost
0 ## 1.7.0
1
2 This is a feature and bugfix release.
3
4 * [SERVER-1695](https://tickets.puppetlabs.com/browse/SERVER-1695) Add an
5 optional `request-body-max-size` setting for restricting the maximum
6 `Content-Length` allowed for requests.
7 * [TK-429](https://tickets.puppetlabs.com/browse/TK-429) Fix for the ability
8 to gzip-encode response bodies when an access log is configured.
9
10 ## 1.6.0
11
12 This is a "feature" release.
13
14 * Added a new function `request-path` to the WebsocketProtocol
15 * [TK-410](https://tickets.puppetlabs.com/browse/TK-410) Add the i18n library
16 as a dependency and use it to externalize strings.
17
18 ## 1.5.10
19
20 This is a maintenance release.
21
22 * Remove unneeded logback-access dependency
23
24 ## 1.5.9
25
26 This is a maintenance release.
27
28 * Upgrade ring-servlet and related dependencies to 1.4.0
29
30 ## 1.5.8
31
32 This is a maintenance release.
33
34 * Upgrade java.jmx dependency to 0.3.1
35
36 ## 1.5.7
37
38 This is a bugfix release.
39
40 * [TK-372](https://tickets.puppetlabs.com/browse/TK-372) Fix a memory leak that
41 occurred when a SIGHUP was used to restart services and at least one webserver
42 has Jetty's JMX metrics enabled.
43
44 ## 1.5.6
45
46 This is a security release.
47
48 * [TK-343](https://tickets.puppetlabs.com/browse/TK-343) Support a new
49 option for handler registrations, `normalize-request-uri`, which can be
50 used to request that the URI path component is sanitized before the
51 handler is invoked for a request and that `.getRequestURI` calls made by
52 the handler return a path that has been percent-decoded.
53
54 ## 1.5.5
55
56 This is a bugfix and maintenance release.
57
58 * [TK-333](https://tickets.puppetlabs.com/browse/TK-333) Tolerate multiple
59 calls to `stop` by ensuring that the server shuts down and cleans up mbeans in
60 an idempotent way.
61 * Upgrade Trapperkeeper dependency to 1.3.1
62 * Upgrade Clojure dependency to 1.7.0
63
64 ## 1.5.4
65
66 This is a bugfix release.
67
68 * [TK-338](https://tickets.puppetlabs.com/browse/TK-338) Handle the
69 `TimeoutException` that Jetty throws if its `stopTimeout` is reached
70 before it can gracefully complete all of the open requests. Ensures
71 that the server will be restarted during a HUP even if the timeout
72 occurs.
73
74 ## 1.5.3
75
76 This version number was burned due to an error during the release/deploy
77 process.
78
79 ## 1.5.2
80
81 This is a maintenance release.
82
83 * Make `org.clojure/java.jmx` a top-level dependency so that it can be
84 pulled in automatically via a transitive dependency by consumers of the
85 testutils jar.
86
87 ## 1.5.1
88
89 This is a bugfix release.
90
91 * [TK-301](https://tickets.puppetlabs.com/TK-301) Fix a memory leak related
92 to Jetty's JMX metrics; this leak is only relevant if using the recent
93 HUP support released in Trapperkeeper 1.3.0.
94
95 ## 1.5.0
96
97 This is a "feature" release.
98
99 * Added new function `get-server` to web routing service.
100
101 ## 1.4.1
102
103 This is a bugfix release.
104
105 * [TK-270](https://tickets.puppetlabs.com/TK-270) Fix a bug that prevented
106 the use of 1-arity WebsocketProtocol/close!.
107
108 ## 1.4.0
109
110 This is a feature and maintenance release.
111
112 * [TK-247](https://tickets.puppetlabs.com/TK-247) Added tests for Path
113 Traversal Attacks.
114 * Add experimental support for websockets via the `add-websockets-handler`
115 function in the WebserverService and WebroutingService and corresponding
116 client protocol WebsocketProtocol.
117 * Updated ssl-utils dependency to 0.8.1
118
119 ## 1.3.1
120
121 This is a maintenance release.
122
123 * [TK-195](https://tickets.puppetlabs.com/browse/TK-195) Update prismatic
124 dependencies to the latest versions
125
126 ## 1.3.0
127
128 This is a "feature" and security release.
129
130 * [TK-178](https://tickets.puppetlabs.com/browse/TK-178) Upgraded Jetty version
131 dependency to v9.2.10. Jetty v9.2.10 includes changes made in the Jetty
132 v9.2.9 release to address a critical security vulnerability with data
133 potentially being leaked across requests. See https://dev.eclipse.org/mhonarc/lists/jetty-announce/msg00074.html
134 for more information. For a rollup of changes included in the Jetty v9.2.10
135 release, see https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/VERSION.txt.
136
137 * [TK-168](https://tickets.puppetlabs.com/browse/TK-168) Default values for
138 several settings will now derive from the underlying defaults that Jetty would
139 use. This effectively changes the defaults for the following settings:
140
141 - `shutdown-timeout-seconds` in `webserver` section - 60 seconds -> 30 seconds
142
143 - `:idle-timeout` for `add-proxy-route` - 60 seconds -> 30 seconds
144
145 * [TK-148](https://tickets.puppetlabs.com/browse/TK-148) Several related
146 changes:
147
148 - Default for `max-threads` in `webserver` section changed from 100 to
149 200.
150
151 - Exposed new settings for configuring the number of `acceptor-threads`
152 and `selector-threads` that a Jetty webserver will use.
153
154 - Removed work which would automatically bump the server's `max-threads` up
155 to the minimum needed for the server to boot for the case that `max-threads`
156 had not been configured but the server's minimum needed threads had
157 exceeded the default `max-threads`. The original work which enabled the
158 automatic bump had been done in [TK-130](https://tickets.puppetlabs.com/browse/TK-130).
159
160 ## 1.2.0
161
162 This is a feature release.
163
164 * Upgrade to version 9.2.8 of upstream Jetty. We were previously at
165 v9.1.0, which was over a year old. The newer version contains some
166 performance improvements and bug fixes for potential networking
167 issues.
168 * [TK-140](https://tickets.puppetlabs.com/browse/TK-140)
169 Expose new `so-linger-seconds` setting, which can be used to adjust the TCP
170 SO_LINGER time.
171 * [TK-144](https://tickets.puppetlabs.com/browse/TK-144)
172 Expose new `post-config-script` setting; this is for advanced / edge-case
173 configuration needs. If you need to modify a Jetty setting that we don't
174 expose in our own config, you can provide a snippet of Java code to access
175 the Jetty Server object directly and modify additional settings.
176 * [TK-133](https://tickets.puppetlabs.com/browse/TK-133)
177 Support comma-delimited strings for the config value for `ssl-protocols`
178 and `cipher-suites`. This allows these settings to be used with older
179 config file formats, such as ini.
180 * [TK-151](https://tickets.puppetlabs.com/browse/TK-151)
181 Expose new `idle-timeout-milliseconds` setting, which can be used to tell
182 Jetty to forcefully close a client connection if it is idle for a specified
183 amount of time.
184
185 ## 1.1.1
186
187 * [TK-82](https://tickets.puppetlabs.com/browse/TK-82)
188 Add configuration option to control maximum number of
189 open HTTP connections that Jetty will maintain.
190 * Upgrade trapperkeeper dependency to 1.0.1.
191 * Upgrade jvm-ssl-utils (previously known as jvm-certificate-authority)
192 dependency to 0.7.0.
193
194 ## 1.1.0
195
196 * [TK-130](https://tickets.puppetlabs.com/browse/TK-130)
197 The default value for Jetty's maximum threadpool size is now
198 calculated to ensure it can start up on a box with a large
199 number of cores.
200
201 ## 1.0.1
202
203 * This release adds an additional configuration option to
204 `add-proxy-route` ([TK-110](https://tickets.puppetlabs.com/browse/TK-110)).
205
206 ## 1.0.0
207
208 * Promoting previous version to 1.0.0 so that we can begin to
209 be more deliberate about adhering to semver in the future.
210
211 ## 0.9.0
212
213 This is a security release.
214
215 * [TK-96](https://tickets.puppetlabs.com/browse/TK-96): Define
216 a default set of SSL protocols that the server should allow
217 (TLSv1, TLSv1.1, TLSv1.2) and use them if the user doesn't
218 explicitly set the `ssl-protocols` setting.
219
220 ## 0.8.1
221
222 This is a minor bugfix release.
223
224 * Fix an issue wherein the default graceful shutdown
225 timeout was not being set to 60 seconds.
226
227 ## 0.8.0
228
229 * Adds a new option, `:redirect-if-no-trailing-slash`,
230 that determines whether or not a 302 response will be
231 returned when making requests to endpoints with registered
232 handlers without a trailing slash on the end.
233 * By default, requests will now route through to a handler
234 when no trailing slash is present on the request URL rather
235 than returning a 302 response (which was the behavior in
236 previous versions).
237 * Adds graceful shutdown support and a new option to the
238 webserver config, `shutdown-timeout-seconds`, that allows
239 users to set the stop timeout of the Jetty server.
240
241 ## 0.7.7
242
243 This is a minor feature and bugfix release.
244
245 * Improves various error messages thrown by the
246 Webrouting and Webserver services.
247 * Changes the data structure output by the
248 `get-registered-endpoints` and `log-registered-endpoints`
249 functions. Now, a map will be output where each key is
250 an endpoint, with its value being an array containing
251 information on every handler registered at that endpoint.
252 * Adds a new option to the webserver configuration,
253 `access-log-config`, that allows configuration of request
254 logging.
255 * [TK-84](https://tickets.puppetlabs.com/browse/TK-84)
256 Query parameters were not being decoded when the URI was
257 being rewritten in the reified ProxyServlet class, meaning
258 they would get double encoded.
259 * Adds a new option to `add-proxy-route`,
260 `failure-callback-fn`, which allows customization of
261 HTTP Error Responses.
262
263 ## 0.7.6
264
265 This is a dead release.
266
267 ## 0.7.5
268
269 This is a minor feature release.
270
271 * [TK-75](https://tickets.puppetlabs.com/browse/TK-75)
272 Adds a new option `gzip-enable` that can be used to
273 enable/disable support for gzipping responses to
274 requests that include an appropriate `Accept-Encoding`
275 header.
276
277 ## 0.7.4
278
279 This is a minor feature release.
280
281 * Adds a new option to both the `static-content` configuration
282 setting in the webserver config and to the add-context-handler
283 service function that allows symlinks to be followed when serving
284 static content.
285
286 ## 0.7.3
287
288 This is a minor feature release.
289
290 * Adds a new, optional `static-content` configuration setting to the
291 webserver config. This setting allows you to serve files on disk
292 or resources in a jar as static assets at a given URL prefix,
293 all via configuration.
294
295 ## 0.7.2
296
297 This is a minor, backward-compatible feature and bugfix release.
298
299 * [TK-58](https://tickets.puppetlabs.com/browse/TK-58):
300 `default-server` support did not work for some functions, such as
301 `get-registered-endpoints`.
302 * Add support for SSL certificate chains, and new setting `ssl-cert-chain`
303 * Upgrade to Trapperkeeper 0.5.1
304
305 ## 0.7.1
306
307 * [TK-53](https://tickets.puppetlabs.com/browse/TK-53):
308 Add a `get-route` function to web routing service.
309 * [TK-33](https://tickets.puppetlabs.com/browse/TK-33):
310 Add support for configuring proxy routes to automatically
311 follow redirects from the remote server.
312 * In proxy configuration, add support for a callback function that
313 can rewrite the URI before the request is proxied.
314 * [TK-45](https://tickets.puppetlabs.com/browse/TK-45):
315 Add support for strings in addition to keywords when specifying the
316 URI scheme for proxy requests.
317
318 ## 0.7.0
319
320 * [TK-50](https://tickets.puppetlabs.com/browse/TK-50):
321 Changes to "default" server handling in a multi-server configuration:
322 * It is no longer required to specify a default server. If a service function
323 is called without specifying a `server-id` when there are multiple servers
324 configured, an error will be thrown.
325 * It is no longer required that the default server be named `default`;
326 instead it is configured by specifying `default-server: true`
327 in the configuration for the given server.
328 * [TK-51](https://tickets.puppetlabs.com/browse/TK-51):
329 Added the ability to the specify `server-id` in the `WebroutingService`
330 configuration, instead of forcing it to be done in code.
331 * Minor bug fixes and improvements:
332 * [TK-48](https://tickets.puppetlabs.com/browse/TK-48),
333 [TK-44](https://tickets.puppetlabs.com/browse/TK-44)
334
335
336 ## 0.6.1
337 * Add configuration option `request-header-max-size`
338 * Increase default buffer sizes for request and response
339 * Update test dependencies to latest version of puppetlabs/http-client (0.2.1)
340
341 ## 0.6.0
342 * The `WebserverService` can now run multiple Jetty servers, on different ports.
343 * Added a new `WebroutingService` to provide a centralized, configuration-based
344 way to configure all of the URL paths at which services will register web applications
345 (Ring handlers, Servlets, etc.)
346 * Added JMX reporting to the `jetty9-service`
347 * Added `get-registered-endpoints` and `log-registered-endpoints` functions
348 to the `WebserverService`
349 * Minor bug fixes and improvements:
350 * [TK-21](https://tickets.puppetlabs.com/browse/TK-21),
351 [TK-22](https://tickets.puppetlabs.com/browse/TK-22),
352 [TK-31](https://tickets.puppetlabs.com/browse/TK-31),
353 [TK-43](https://tickets.puppetlabs.com/browse/TK-43)
354 * Upgraded trapperkeeper dependency to version 0.4.3
355 * Upgraded kitchensink dependency to version 0.7.2
356
357 ## 0.5.2
358 * Update trapperkeeper dependency to version 0.4.2.
359 * Update kitchensink dependency to version 0.7.1.
360 * Update certificate-authority dependency to 0.1.5.
361 * Update http-client dev dependency to 0.1.7.
362 * Stop is now called on the Jetty Server instance if an error occurs in Jetty
363 code while the server is starting up. This allows the process running
364 Trapperkeeper to shut down properly after such an error has occurred.
365 * Validation of the webserver configuration is now done via the use of
366 Prismatic Schema.
367 * A new webserver option, `ssl-crl-path`, can be used to configure a
368 Certificate Revocation List that Jetty would use to validate client
369 certificates for incoming SSL connections.
370
371 ## 0.5.1
372 * Upgrade trapperkeeper dependency to version 0.3.12
373 * Upgrade kitchensink dependency to version 0.7.0
374 * Replace clj-http dependency with [puppetlabs/http-client](https://github.com/puppetlabs/clj-http-client)
375 * Update test/example configuration files to use HOCON instead of .ini files
376
377 ## 0.5.0
378 * Added new function `override-webserver-settings!`, which allows another
379 service to provide overridden values for the webserver configuration.
380 * Update to latest version of puppetlabs/kitchensink
381 * Use puppetlabs/certificate-authority for all SSL-related tasks
382
383 ## 0.4.0
384 * Added new function `add-proxy-route`, which supports configuring the server to
385 work as a reverse proxy for certain routes
386
387 ## 0.3.5
388 * Added a new service function, `add-context-handler`, which supports registering
389 a context handler for static content, with optional support for context listeners
390 via the `javax.servlet.ServletContextListener` interface.
391
392 ## 0.3.4
393 * Added support for registering WAR files via the `add-war-handler` service function.
394 * Moved server creation from the `init` life cycle to the `start` life cycles.
395
396 ## 0.3.3
397 * Fix bug where even if no http `port` was specified in the webserver config,
398 the Jetty webserver was still opening an http binding on port 8080. An
399 http port binding will now be opened only if a `port` is specified in the
400 config file.
401 * A config file can now optionally include a `client-auth` webserver setting.
402 The setting specifies how the server validates the client certificate
403 during the setup of an SSL connection. The default behavior if the setting
404 is not specified is the same as with prior releases; the server will
405 require that the SSL client provide a certificate and that the certificate
406 be valid. For more information, refer to the [jetty-config.md]
407 (doc/jetty-config.md) document.
0 # How to contribute
1
2 Third-party patches are essential for keeping puppet open-source projects
3 great. We want to keep it as easy as possible to contribute changes that
4 allow you to get the most out of our projects. There are a few guidelines
5 that we need contributors to follow so that we can have a chance of keeping on
6 top of things. For more info, see our canonical guide to contributing:
7
8 [https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md](https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md)
0 Apache License
1 Version 2.0, January 2004
2 http://www.apache.org/licenses/
3
4 TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
5
6 1. Definitions.
7
8 "License" shall mean the terms and conditions for use, reproduction,
9 and distribution as defined by Sections 1 through 9 of this document.
10
11 "Licensor" shall mean the copyright owner or entity authorized by
12 the copyright owner that is granting the License.
13
14 "Legal Entity" shall mean the union of the acting entity and all
15 other entities that control, are controlled by, or are under common
16 control with that entity. For the purposes of this definition,
17 "control" means (i) the power, direct or indirect, to cause the
18 direction or management of such entity, whether by contract or
19 otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 outstanding shares, or (iii) beneficial ownership of such entity.
21
22 "You" (or "Your") shall mean an individual or Legal Entity
23 exercising permissions granted by this License.
24
25 "Source" form shall mean the preferred form for making modifications,
26 including but not limited to software source code, documentation
27 source, and configuration files.
28
29 "Object" form shall mean any form resulting from mechanical
30 transformation or translation of a Source form, including but
31 not limited to compiled object code, generated documentation,
32 and conversions to other media types.
33
34 "Work" shall mean the work of authorship, whether in Source or
35 Object form, made available under the License, as indicated by a
36 copyright notice that is included in or attached to the work
37 (an example is provided in the Appendix below).
38
39 "Derivative Works" shall mean any work, whether in Source or Object
40 form, that is based on (or derived from) the Work and for which the
41 editorial revisions, annotations, elaborations, or other modifications
42 represent, as a whole, an original work of authorship. For the purposes
43 of this License, Derivative Works shall not include works that remain
44 separable from, or merely link (or bind by name) to the interfaces of,
45 the Work and Derivative Works thereof.
46
47 "Contribution" shall mean any work of authorship, including
48 the original version of the Work and any modifications or additions
49 to that Work or Derivative Works thereof, that is intentionally
50 submitted to Licensor for inclusion in the Work by the copyright owner
51 or by an individual or Legal Entity authorized to submit on behalf of
52 the copyright owner. For the purposes of this definition, "submitted"
53 means any form of electronic, verbal, or written communication sent
54 to the Licensor or its representatives, including but not limited to
55 communication on electronic mailing lists, source code control systems,
56 and issue tracking systems that are managed by, or on behalf of, the
57 Licensor for the purpose of discussing and improving the Work, but
58 excluding communication that is conspicuously marked or otherwise
59 designated in writing by the copyright owner as "Not a Contribution."
60
61 "Contributor" shall mean Licensor and any individual or Legal Entity
62 on behalf of whom a Contribution has been received by Licensor and
63 subsequently incorporated within the Work.
64
65 2. Grant of Copyright License. Subject to the terms and conditions of
66 this License, each Contributor hereby grants to You a perpetual,
67 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
68 copyright license to reproduce, prepare Derivative Works of,
69 publicly display, publicly perform, sublicense, and distribute the
70 Work and such Derivative Works in Source or Object form.
71
72 3. Grant of Patent License. Subject to the terms and conditions of
73 this License, each Contributor hereby grants to You a perpetual,
74 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
75 (except as stated in this section) patent license to make, have made,
76 use, offer to sell, sell, import, and otherwise transfer the Work,
77 where such license applies only to those patent claims licensable
78 by such Contributor that are necessarily infringed by their
79 Contribution(s) alone or by combination of their Contribution(s)
80 with the Work to which such Contribution(s) was submitted. If You
81 institute patent litigation against any entity (including a
82 cross-claim or counterclaim in a lawsuit) alleging that the Work
83 or a Contribution incorporated within the Work constitutes direct
84 or contributory patent infringement, then any patent licenses
85 granted to You under this License for that Work shall terminate
86 as of the date such litigation is filed.
87
88 4. Redistribution. You may reproduce and distribute copies of the
89 Work or Derivative Works thereof in any medium, with or without
90 modifications, and in Source or Object form, provided that You
91 meet the following conditions:
92
93 (a) You must give any other recipients of the Work or
94 Derivative Works a copy of this License; and
95
96 (b) You must cause any modified files to carry prominent notices
97 stating that You changed the files; and
98
99 (c) You must retain, in the Source form of any Derivative Works
100 that You distribute, all copyright, patent, trademark, and
101 attribution notices from the Source form of the Work,
102 excluding those notices that do not pertain to any part of
103 the Derivative Works; and
104
105 (d) If the Work includes a "NOTICE" text file as part of its
106 distribution, then any Derivative Works that You distribute must
107 include a readable copy of the attribution notices contained
108 within such NOTICE file, excluding those notices that do not
109 pertain to any part of the Derivative Works, in at least one
110 of the following places: within a NOTICE text file distributed
111 as part of the Derivative Works; within the Source form or
112 documentation, if provided along with the Derivative Works; or,
113 within a display generated by the Derivative Works, if and
114 wherever such third-party notices normally appear. The contents
115 of the NOTICE file are for informational purposes only and
116 do not modify the License. You may add Your own attribution
117 notices within Derivative Works that You distribute, alongside
118 or as an addendum to the NOTICE text from the Work, provided
119 that such additional attribution notices cannot be construed
120 as modifying the License.
121
122 You may add Your own copyright statement to Your modifications and
123 may provide additional or different license terms and conditions
124 for use, reproduction, or distribution of Your modifications, or
125 for any such Derivative Works as a whole, provided Your use,
126 reproduction, and distribution of the Work otherwise complies with
127 the conditions stated in this License.
128
129 5. Submission of Contributions. Unless You explicitly state otherwise,
130 any Contribution intentionally submitted for inclusion in the Work
131 by You to the Licensor shall be under the terms and conditions of
132 this License, without any additional terms or conditions.
133 Notwithstanding the above, nothing herein shall supersede or modify
134 the terms of any separate license agreement you may have executed
135 with Licensor regarding such Contributions.
136
137 6. Trademarks. This License does not grant permission to use the trade
138 names, trademarks, service marks, or product names of the Licensor,
139 except as required for reasonable and customary use in describing the
140 origin of the Work and reproducing the content of the NOTICE file.
141
142 7. Disclaimer of Warranty. Unless required by applicable law or
143 agreed to in writing, Licensor provides the Work (and each
144 Contributor provides its Contributions) on an "AS IS" BASIS,
145 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
146 implied, including, without limitation, any warranties or conditions
147 of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
148 PARTICULAR PURPOSE. You are solely responsible for determining the
149 appropriateness of using or redistributing the Work and assume any
150 risks associated with Your exercise of permissions under this License.
151
152 8. Limitation of Liability. In no event and under no legal theory,
153 whether in tort (including negligence), contract, or otherwise,
154 unless required by applicable law (such as deliberate and grossly
155 negligent acts) or agreed to in writing, shall any Contributor be
156 liable to You for damages, including any direct, indirect, special,
157 incidental, or consequential damages of any character arising as a
158 result of this License or out of the use or inability to use the
159 Work (including but not limited to damages for loss of goodwill,
160 work stoppage, computer failure or malfunction, or any and all
161 other commercial damages or losses), even if such Contributor
162 has been advised of the possibility of such damages.
163
164 9. Accepting Warranty or Additional Liability. While redistributing
165 the Work or Derivative Works thereof, You may choose to offer,
166 and charge a fee for, acceptance of support, warranty, indemnity,
167 or other liability obligations and/or rights consistent with this
168 License. However, in accepting such obligations, You may act only
169 on Your own behalf and on Your sole responsibility, not on behalf
170 of any other Contributor, and only if You agree to indemnify,
171 defend, and hold each Contributor harmless for any liability
172 incurred by, or claims asserted against, such Contributor by reason
173 of your accepting any such warranty or additional liability.
174
175 END OF TERMS AND CONDITIONS
176
177 APPENDIX: How to apply the Apache License to your work.
178
179 To apply the Apache License to your work, attach the following
180 boilerplate notice, with the fields enclosed by brackets "{}"
181 replaced with your own identifying information. (Don't include
182 the brackets!) The text should be enclosed in the appropriate
183 comment syntax for the file format. We also recommend that a
184 file or class name and description of purpose be included on the
185 same "printed page" as the copyright notice for easier
186 identification within third-party archives.
187
188 Copyright {yyyy} {name of copyright owner}
189
190 Licensed under the Apache License, Version 2.0 (the "License");
191 you may not use this file except in compliance with the License.
192 You may obtain a copy of the License at
193
194 http://www.apache.org/licenses/LICENSE-2.0
195
196 Unless required by applicable law or agreed to in writing, software
197 distributed under the License is distributed on an "AS IS" BASIS,
198 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
199 See the License for the specific language governing permissions and
200 limitations under the License.
201
0 {
1 "version": 1,
2 "file_format": "This MAINTAINERS file format is described at http://pup.pt/maintainers",
3 "issues": "https://tickets.puppetlabs.com/browse/TK",
4 "internal_list": "https://groups.google.com/a/puppet.com/forum/?hl=en#!forum/discuss-trapperkeeper-maintainers",
5 "people": [
6 {
7 "github": "camlow325",
8 "email": "jeremy.barlow@puppet.com",
9 "name": "Jeremy Barlow"
10 },
11 {
12 "github": "cprice404",
13 "email": "chris@puppet.com",
14 "name": "Chris Price"
15 },
16 {
17 "github": "senior",
18 "email": "ryan.senior@puppet.com",
19 "name": "Ryan Senior"
20 },
21 {
22 "github": "pcarlisle",
23 "email": "patrick.carlisle@puppet.com",
24 "name": "Patrick Carlisle"
25 },
26 {
27 "github": "KevinCorcoran",
28 "email": "kevin.corcoran@puppet.com",
29 "name": "Kevin Corcoran"
30 }
31 ]
32 }
0 include dev-resources/Makefile.i18n
0 [![Build Status](https://travis-ci.org/puppetlabs/trapperkeeper-webserver-jetty9.png?branch=master)](https://travis-ci.org/puppetlabs/trapperkeeper-webserver-jetty9)
1
2 ## Trapperkeeper Webserver Service
3
4 This project provides a webserver service for use with the
5 [trapperkeeper service framework](https://github.com/puppetlabs/trapperkeeper)
6 To use this service in your trapperkeeper application, simply add this
7 project as a dependency in your leiningen project file:
8
9 [![Clojars Project](http://clojars.org/puppetlabs/trapperkeeper-webserver-jetty9/latest-version.svg)](http://clojars.org/puppetlabs/trapperkeeper-webserver-jetty9)
10
11 Then add the webserver service to your [`bootstrap.cfg`](https://github.com/puppetlabs/trapperkeeper#bootstrapping)
12 file, via:
13
14 puppetlabs.trapperkeeper.services.webserver.jetty9-service/jetty9-service
15
16 Note that this implementation of the
17 `:WebserverService` interface is based on Jetty 9, which contains performance
18 improvements over previous versions of Jetty that may be significant depending on
19 your application. This service requires JRE 1.7 or greater;
20 however, the interface is intended to be agnostic to the underlying web server
21 implementation. We also provide a
22 [Jetty 7 version of the service](https://github.com/puppetlabs/trapperkeeper-webserver-jetty7),
23 which can be used interchangeably with this one and will support older JDKs.
24 You should only need to change your lein dependencies and your `bootstrap.cfg`
25 file--no code changes.
26
27 The web server is configured via the
28 [trapperkeeper configuration service](https://github.com/puppetlabs/trapperkeeper#configuration-service);
29 so, you can control various properties of the server (ports, SSL, etc.) by adding a `webserver`
30 section to one of your Trapperkeeper configuration files, and setting various properties
31 therein. For more info, see [Configuring the Webserver](doc/jetty-config.md). It is possible to configure
32 both a single webserver or multiple webservers.
33
34 The `webserver-service` currently supports web applications built using
35 Clojure's [Ring](https://github.com/ring-clojure/ring) library and Java's Servlet
36 API. There is also an experimental webserver service that supports loading
37 ruby Rack applications via JRuby; for more info, see the
38 [trapperkeeper-ruby](https://github.com/puppetlabs/trapperkeeper-ruby) project.
39
40 ### Example code
41
42 Four examples are included with this project:
43
44 * A Ring example ([source code](./examples/ring_app))
45 * A Java servlet example ([source code](./examples/servlet_app))
46 * A WAR example ([source code](./examples/war_app))
47 * A multiserver configuration example ([source code](./examples/multiserver_app))
48
49 ### Service Protocol
50
51 This is the protocol for the current implementation of the `:WebserverService`:
52
53 ```clj
54 (defprotocol WebserverService
55 (add-context-handler [this base-path context-path] [this base-path context-path options])
56 (add-ring-handler [this handler path] [this handler path options])
57 (add-websocket-handler [this handlers path] [this handler path options])
58 (add-servlet-handler [this servlet path] [this servlet path options])
59 (add-war-handler [this war path] [this war path options])
60 (add-proxy-route [this target path] [this target path options])
61 (override-webserver-settings! [this overrides] [this server-id overrides])
62 (get-registered-endpoints [this] [this server-id])
63 (log-registered-endpoints [this] [this server-id])
64 (join [this] [this server-id])
65 ```
66
67 Here is a bit more info about each of these functions:
68
69 #### `add-ring-handler`
70
71 `add-ring-handler` takes two arguments: `[handler path]`. The `handler` argument
72 is just a normal Ring application (the same as what you would pass to `run-jetty`
73 if you were using the `ring-jetty-adapter`). The `path` is a URL prefix / context
74 string that will be prepended to all your handler's URLs; this is key to allowing
75 the registration of multiple handlers in the same web server without the possibility
76 of URL collisions. So, for example, if your ring handler has routes `/foo` and
77 `/bar`, and you call:
78
79 ```clj
80 (add-ring-handler my-app "/my-app")
81 ```
82
83 Then your routes will be served at `/my-app/foo` and `my-app/bar`.
84
85 You may specify `""` as the value for `path` if you are only registering a single
86 handler and do not need to prefix the URL.
87
88 There is also a three argument version of this function which takes these arguments:
89 `[handler path options]`. `options` is a map containing three optional keys.
90
91 The first is
92 `:server-id`, which specifies which server you want to add the ring-handler to. If
93 `:server-id` is specified, the ring handler will be added to the server with id
94 `:server-id`. If no `:server-id` is specified, or the two argument version is called,
95 the ring handler will be added to the default server. Calling the two-argument version or
96 leaving out `:server-id` will not work in a multiserver set-up if no default server is specified.
97
98 The second optional argument is `:redirect-if-no-trailing-slash`. When set to `true`,
99 all requests made to the endpoint at which the ring-handler was registered will, if
100 no trailing slash is present, return a 302 redirect response to the same URL but with a trailing slash
101 added. If the option is set to `false`, no redirect will occur, and the request will be
102 routed through to the registered handler. This option defaults to `false`.
103
104 The third optional argument is `:normalize-request-uri`. When set to `true`, the
105 URI made available to the ring handler request map via the `:uri` key will have
106 been "normalized". See the [Request URI Normalization]
107 (#request-uri-normalization) section for more information on the
108 normalization process. When set to `false` (the default value), the raw path
109 component from the HTTP request URI will be the value for the `:uri` key.
110
111 Here's an example of how to use the `:WebserverService`:
112
113 ```clj
114 (defservice MyWebService
115 [[:WebserverService add-ring-handler]]
116 ;; initialization
117 (init [this context]
118 (add-ring-handler my-app "/my-app")
119 context))
120 ```
121
122 This would add your ring handler to the default server at endpoint "/my-app".
123 Alternatively, if you did this:
124
125 ```clj
126 (defservice MyWebService
127 [[:WebserverService add-ring-handler]]
128 ;; initialization
129 (init [this context]
130 (add-ring-handler my-app "/my-app" {:server-id :foo})
131 context))
132 ```
133 it would add your ring handler to the server with id `:foo` at endpoint "/my-app",
134 rather than the default server.
135
136 *NOTE FOR COMPOJURE APPS*: If you are using compojure, it's important to note
137 that compojure requires use of the [`context` macro](https://github.com/weavejester/compojure/wiki/Nesting-routes)
138 in order to support nested routes. So, if you're not already using `context`,
139 you will need to do something like this:
140
141 ```clj
142 (ns foo
143 (:require [compojure.core :as c]
144 ;;...
145 ))
146
147 (defservice MyWebService
148 [[:WebserverService add-ring-handler]]
149 ;; initialization
150 (init [this svc-context]
151 (let [context-path "/my-app"
152 context-app (c/context context-path [] my-compojure-app)]
153 (add-ring-handler context-app context-path))
154 svc-context))
155 ```
156
157 ##### Request URI Normalization
158
159 The `:normalize-request-uri` setting, which can be provided in the `options`
160 argument for an add handler call, controls whether or not the
161 [path component](https://tools.ietf.org/html/rfc3986#section-3.3) from the HTTP
162 request URI is normalized. The value for the setting is expected to be a
163 boolean.
164
165 When set to `false` (the default value), the "raw" path component will be
166 used by the webserver when evaluating a request to the handler.
167
168 When set to `true`, the path component that the webserver evaluates for a
169 request to the handler will have been "normalized". For a Ring request handler,
170 the "normalized" value (instead of the "raw" value) will be associated with the
171 `:uri` key in the Ring request map. For a Servlet request handler, the
172 "normalized" value (instead of the "raw" value) will be returned from a call
173 made to the `getRequestURI` method on the `HttpServletRequest` object.
174
175 The following steps, in order, are performed against the raw path component
176 when the `:normalize-request-uri` setting is true:
177
178 1. URL (percent) decode the path, assuming any percent-encodings represent UTF-8
179 characters.
180
181 For example:
182
183 ```
184 /foo//bar/%2E%2E/ba%7A => /foo//bar/../baz
185 ```
186
187 If a non-percent encoded semicolon character, U+003B, is found in the path
188 during the percent decoding step, that character and all following characters
189 will be removed from the resulting path.
190
191 For example:
192
193 ```
194 /foo//bar/%2E%2E/ba%7A;bim => /foo//bar/../baz
195 ```
196
197 Requests intending to include a semicolon in the path should percent-encode
198 the semicolon. In this case, the server will preserve the semicolon after the
199 decoding step.
200
201 For example:
202
203 ```
204 /foo//bar/%2E%2E/ba%7A%3Bbim => /foo//bar/../baz;bim
205 ```
206
207 If the request has malformed content, e.g., partially-formed percent-encoded
208 characters like '%A%B', an HTTP 400 (Bad Request) error will be returned.
209
210 2. Check the percent-decoded path for any relative path segments ('..' or
211 '.').
212
213 If one or more relative path segments are found, an HTTP 400 (Bad Request)
214 error will be returned.
215
216 For example, an error would be returned for any of the following paths:
217
218 ```
219 .
220 ..
221 /foo//bar/../baz
222 /foo//./bar/baz
223 ```
224
225 The following paths would not be considered to contain relative paths:
226
227 ```
228 /foo//bar/baz
229 /foo//bar/.../baz
230 /foo//bar/a.b/baz
231 /foo//bar/a..b/baz
232 ```
233
234 3. Compact any repeated forward slash characters in a path.
235
236 For example:
237
238 ```
239 /foo//bar/baz => /foo/bar/baz
240 /foo/bar////baz => /foo/bar/baz
241 ```
242
243 The following example shows the result after normalization of a URI request
244 path which includes repeated forward slash characters which have been
245 percent-encoded:
246
247 ```
248 /foo%2F%2Fbar/ba%7A => /foo/bar/baz
249 ```
250
251 #### `add-context-handler`
252
253 `add-context-handler` takes two arguments: `[base-path context-path]`. The `base-path`
254 argument is a URL string pointing to a location containing static resources which are
255 made accessible at the `context-path` URL prefix.
256
257 For example, to make your CSS files stored in the `resources/css` directory available
258 at `/css`:
259
260 ```clj
261 (defservice MyWebService
262 [[:WebserverService add-context-handler]]
263 ;; initialization
264 (init [this context]
265 (add-context-handler "resources/css" "/css")
266 context))
267 ```
268
269 There is also a three argument version of the function which takes these arguments:
270 `[base-path context-path options]`, where the first two arguments are the
271 same as in the two argument version and `options` is a map containing five optional keys,
272 `:server-id`, `:redirect-if-no-trailing-slash`, `:normalize-request-uri`,
273 `:follow-links`, and `:context-listeners`.
274
275 The value stored in `:server-id` specifies which server
276 to add the context handler to, similar to how it is done in `add-ring-handler`. Again, like
277 `add-ring-handler`, if this key is absent or the two argument version is called, the context handler
278 will be added to the default server. Calling the two-argument version or leaving out `:server-id`
279 will not work in a multiserver set-up if no default server is specified.
280
281 The value stored in `:redirect-if-no-trailing-slash` is a boolean indicating whether or not
282 to redirect when a request is made to this handler without a trailing slash, just like with
283 `add-ring-handler`. Again, this defaults to false.
284
285 The value stored in `:normalize-request-uri` is a boolean indicating whether
286 or not the request URI should be normalized before it is made available to the
287 handler. See the [Request URI Normalization](#request-uri-normalization)
288 section for more information on the normalization process.
289
290 The value stored in `:follow-links` is a
291 boolean indicating whether or not symbolic links
292 should be served. The service does NOT serve symbolic links by default.
293
294 The value stored in `:context-listeners` is a list of objects implementing the
295 [ServletContextListener] (http://docs.oracle.com/javaee/7/api/javax/servlet/ServletContextListener.html)
296 interface. These listeners are registered with the context created for serving the
297 static content and receive notifications about the lifecycle events in the context
298 as defined in the ServletContextListener interface. Of particular interest is the
299 `contextInitialized` event notification as it provides access to the configuration
300 of the context through the methods defined in the [ServletContext]
301 (http://docs.oracle.com/javaee/7/api/javax/servlet/ServletContext.html)
302 interface. This opens up wide possibilities for customizing the context - in an
303 extreme case the context originally capable of serving just the static content can
304 be changed through this mechanism to a fully dynamic web application (in fact this
305 very mechanism is used in the [trapperkeeper-ruby]
306 (https://github.com/puppetlabs/trapperkeeper-ruby) project to turn the context into
307 a container for hosting an arbitrary ruby rack application - see [here]
308 (https://github.com/puppetlabs/trapperkeeper-ruby/blob/master/src/clojure/puppetlabs/trapperkeeper/services/rack_jetty/rack_jetty_service.clj)).
309
310 #### `add-servlet-handler`
311
312 `add-servlet-handler` takes two arguments: `[servlet path]`. The `servlet` argument
313 is a normal Java [Servlet](http://docs.oracle.com/javaee/7/api/javax/servlet/Servlet.html).
314 The `path` is the URL prefix at which the servlet will be registered.
315
316 There is also a three argument version of the function which takes these arguments:
317 `[servlet path options]`, where the first two arguments are the same as
318 in the two argument version and options is a map containing four optional keys,
319 `:server-id`, `:redirect-if-no-trailing-slash`, `normalize-request-uri`, and
320 `:servlet-init-params`.
321
322 As in `add-ring-handler`, `:server-id` specifies which server to add
323 the handler to. If `:server-id` is absent or the two-argument function is called, the servlet
324 handler will be added to the default server. Calling the two-argument version or leaving out
325 `:server-id` will not work in a multiserver set-up if no default server is specified.
326
327 The value stored in `:redirect-if-no-trailing-slash` is a boolean indicating whether or not
328 to redirect when a request is made to this handler without a trailing slash, just like with
329 `add-ring-handler`. Again, this defaults to false.
330
331 The value stored in `:normalize-request-uri` is a boolean indicating whether
332 or not the request URI should be normalized before it is made available to the
333 handler. See the [Request URI Normalization](#request-uri-normalization)
334 section for more information on the normalization process.
335
336 The value stored at the `:servlet-init-params` key is a map of servlet init
337 parameters.
338
339 For example, to host a servlet at `/my-app`:
340
341 ```clj
342 (ns foo
343 ;; ...
344 (:import [bar.baz SomeServlet]))
345
346 (defservice MyWebService
347 [[:WebserverService add-servlet-handler]]
348 ;; initialization
349 (init [this context]
350 (add-servlet-handler (SomeServlet. "some config") "/my-app")
351 context))
352 ```
353
354 For more information see the [example servlet app](examples/servlet_app).
355
356 #### `add-websocket-handler`
357
358 NOTE: Websockets support is currently an experimental feature; the
359 API for websockets support may be subject to minor changes in a
360 future release.
361
362 `add-websocket-handler` takes two arguments: `[handlers path]`.
363 The `handlers` is a map of callbacks to invoke when handling a websocket session.
364 The `path` is the URL prefix where this websocket servlet will be registered.
365
366 The possible callbacks for the `handlers` map are:
367
368 ```clj
369 {:on-connect (fn [ws])
370 :on-error (fn [ws error])
371 :on-close (fn [ws status-code reason])
372 :on-text (fn [ws text])
373 :on-bytes (fn [ws bytes offset len])}
374 ```
375
376 Querying data or sending messages over the websocket is supported by
377 the functions of WebSocketProtocol protocol from the
378 `puppetlabs.experimental.websocket.client` namespace:
379
380 ```clj
381 (connected? [this]
382 "Returns a boolean indicating if the session is currently connected")
383 (send! [this msg]
384 "Send a message to the websocket client")
385 (close! [this] [this code reason]
386 "Close the websocket session")
387 (remote-addr [this]
388 "Find the remote address of a websocket client")
389 (ssl? [this]
390 "Returns a boolean indicating if the session was established by wss://")
391 (peer-certs [this]
392 "Returns an array of X509Certs presented by the ssl peer, if any")
393 (request-path [this]
394 "Returns the URI path used in the websocket upgrade request to the server"))
395 ```
396
397 For example, to provide a simple websockets echo service as `/wsecho`:
398
399 ```clj
400 (ns foo
401 (:require [puppetlabs.experimental.websockets.client :as ws-client]))
402
403 (def echo-handlers
404 {:on-text (fn [ws text] (ws-client/send! ws text))})
405
406 (defservice wsecho-webservice
407 [[:WebserverService add-websocket-handler]]
408 (init [this context]
409 (add-websocket-handler echo-handlers "/wsecho")
410 context))
411 ```
412
413 #### `add-war-handler`
414
415 `add-war-handler` takes two arguments: `[war path]`.
416 The `war` is the file path or the URL to a WAR file.
417 The `path` is the URL prefix at which the WAR will be registered.
418
419 For example, to host `resources/cas.war` WAR at `/cas`:
420
421 ```clj
422 (defservice cas-webservice
423 [[:WebserverService add-war-handler]]
424 (init [this context]
425 (add-war-handler "resources/cas.war" "/cas")
426 context))
427 ```
428
429 There is also a three-argument version that takes these parameters:
430 `[war path options]`. `options` is a map containing three optional
431 keys, `:server-id`, `:redirect-if-no-trailing-slash`, and
432 `:normalize-request-uri`.
433
434 As with `add-ring-handler`,
435 this determines which server the handler is added to. If this key is absent or the two argument
436 version is called, the handler will be added to the default server. Calling
437 the two-argument version or leaving out `:server-id` will not work in a
438 multiserver set-up if no default server is specified.
439
440 The value stored in `:redirect-if-no-trailing-slash` is a boolean indicating whether or not
441 to redirect when a request is made to this handler without a trailing slash, just like with
442 `add-ring-handler`. Again, this defaults to false.
443
444 The value stored in `:normalize-request-uri` is a boolean indicating whether
445 or not the request URI should be normalized before it is made available to the
446 handler. See the [Request URI Normalization](#request-uri-normalization)
447 section for more information on the normalization process.
448
449 #### `add-proxy-route`
450
451 `add-proxy-route` is used to configure certain the server as a reverse proxy for
452 certain routes. This function will accept two or three arguments: `[target path]`, or
453 `[target path options]`.
454
455 `path` is the URL prefix for requests that you wish to proxy.
456
457 `target` is a map that controls how matching requests will be proxied; here are
458 the keys required in the `target` map:
459
460 * `:host`: required; a string representing the host or IP to proxy requests to.
461 * `:port`: required; an integer representing the port on the remote host that requests
462 should be proxied to.
463 * `:path`: required; the URL prefix that should be prepended to all proxied requests.
464
465 `options`, if provided, is a map containing optional configuration for the proxy
466 route:
467
468 * `:scheme`: optional; legal values are `:orig`, `:http`, and `:https`. If you
469 specify `:http` or `:https`, then all proxied requests will use the specified
470 scheme. The default value is `:orig`, which means that proxied requests will
471 use the same scheme as the original request.
472 * `:ssl-config`: optional; may be set to either `:use-server-config` (default) or
473 to a map containing the keys `:ssl-cert`, `:ssl-key`, and `:ssl-ca-cert`, as
474 well as the optional keys `:cipher-suites` and `:protocols`. If
475 `:use-server-config`, then any proxied requests that use HTTPS will use the same
476 SSL context/configuration that the web server is configured with. If you specify
477 a map, then the entries must point to the PEM files that should be used for the
478 SSL context. These keys have the same meaning as they do for the SSL configuration
479 of the main web server.
480 * `:rewrite-uri-callback-fn`: optional; a function to manipulate the rewritten target
481 URI (e.g. change the port, or even change the entire URI) before Jetty continues on
482 with the proxy. The function must accept two arguments, `[target-uri req]`. For more
483 information, see [below](#rewrite-uri-callback-fn).
484 * `:callback-fn`: optional; a function to manipulate the request object (e.g.
485 to add additional headers) before Jetty continues on with the proxy. The
486 function must accept two arguments, `[proxy-req req]`. For more information,
487 see [below](#callback-fn).
488 * `:failure-callback-fn`: optional; a function to manipulate the response object in case of failure.
489 The function must accept four arguments, `[req resp proxy-resp failure]`. For more information,
490 see [below](#failure-callback-fn).
491 * `:request-buffer-size`: optional; an integer value to which to set the size
492 of the request buffer used by the HTTP Client. This allows HTTP requests with
493 very large cookies to go through, as a large cookie can cause the request
494 buffer to overflow unless the size is increased. The default is 4096 bytes.
495 * `:follow-redirects`: optional; a boolean that indicates whether or not the HttpClient
496 created by a ProxyServlet should follow redirects. Defaults to `false`.
497 * `:server-id`: optional; the id of the server to which to add the proxy handler. If absent,
498 the handler will be added to the default server. If the two argument version of this function
499 is called, the handler will also be added to the default server. Leaving out `:server-id` or calling
500 the two argument version of this function will not work in a multiserver set-up if no default server
501 is specified.
502 * `:redirect-if-no-trailing-slash`: optional; a boolean indicating whether or not to redirect
503 when a request is made to this proxy route without a trailing slash, as with `add-ring-handler`.
504 Defaults to false.
505 * `:idle-timeout`: optional; sets the maximum amount of time, measured in seconds, for the proxy to
506 wait for a response from the upstream server. If no response is received within the time
507 specified, then an HTTP 504 error is returned. If this option is not specified then a value
508 of 30 seconds is used.
509
510 Simple example:
511
512 ```clj
513 (defservice foo-service
514 [[:WebserverService add-proxy-route]]
515 (init [this context]
516 (add-proxy-route
517 {:host "localhost"
518 :port 10000
519 :path "/bar"}
520 "/foo")
521 context))
522 ```
523
524 In this example, all incoming requests with a prefix of `/foo` will be proxied
525 to `localhost:10000`, with a prefix of `/bar`, using the same scheme (HTTP/HTTPS)
526 that the original request used, and using the SSL context of the main webserver.
527
528 So, e.g., an HTTPS request to the main webserver at `/foo/hello-world` would be
529 proxied to `https://localhost:10000/bar/hello-world`.
530
531 A slightly more complex example:
532
533 ```clj
534 (defservice foo-service
535 [[:WebserverService add-proxy-route]]
536 (init [this context]
537 (add-proxy-route
538 {:host "localhost"
539 :port 10000
540 :path "/bar"}
541 "/foo"
542 {:scheme :https
543 :ssl-config {:ssl-cert "/tmp/cert.pem"
544 :ssl-key "/tmp/key.pem"
545 :ssl-ca-cert "/tmp/ca.pem"}})
546 context))
547 ```
548
549 In this example, all incoming requests with a prefix of `foo` will be proxied
550 to `https://localhost:10000/bar`. We'll proxy using HTTPS even if the original
551 request was HTTP, and we'll use the three pem files in `/tmp` to configure the
552 HTTPS client, regardless of the SSL configuration of the main web server.
553
554 #####`:rewrite-uri-callback-fn`
555
556 This option lets you provide a function to manipulate the rewritten target URI. The
557 function is called in the overridden implementation of
558 [`rewriteURI`](http://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/proxy/ProxyServlet.html#rewriteURI(javax.servlet.http.HttpServletRequest))
559 method after the target URI is computed. It must take two arguments, `[target-uri req]`, where `target-uri` is a
560 [`URI`](http://docs.oracle.com/javase/7/docs/api/java/net/URI.html)
561 and `req` is an
562 [`HttpServletRequest`](http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html).
563 `target-uri` will be modified and returned by the function.
564
565 An example with a rewrite URI callback function:
566
567 ```clj
568 (defservice foo-service
569 [[:WebserverService add-proxy-route]]
570 (init [this context]
571 (add-proxy-route
572 {:host "localhost"
573 :port 10000
574 :path "/bar"}
575 "/foo"
576 {:rewrite-uri-callback-fn (fn [target-uri req]
577 (if-not (= "GET" (.getMethod req))
578 (URI. "http://localhost:11111/errors/unsupported-method")
579 target-uri))})
580 context))
581 ```
582
583 In this example, all incoming requests with a method other than `GET` will be proxied
584 to `http://localhost:11111/errors/unsupported-method`.
585
586 #####`:callback-fn`
587
588 This option lets you provide a function to manipulate the request object. The
589 function will be passed to the
590 [`customizeProxyRequest`](http://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/proxy/ProxyServlet.html#customizeProxyRequest%28org.eclipse.jetty.client.api.Request,%20javax.servlet.http.HttpServletRequest%29)
591 method. It must take two arguments, `[proxy-req req]`, where `proxy-req` is a
592 [`Request`](http://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/client/api/Request.html)
593 and `req` is an
594 [`HttpServletRequest`](http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html).
595 `proxy-req` will be modified and returned by the function.
596
597 An example with a callback function:
598
599 ```clj
600 (defservice foo-service
601 [[:WebserverService add-proxy-route]]
602 (init [this context]
603 (add-proxy-route
604 {:host "localhost"
605 :port 10000
606 :path "/bar"}
607 "/foo"
608 {:callback-fn (fn [proxy-req req]
609 (.header proxy-req "x-example" "baz"))})
610 context))
611 ```
612
613 In this example, all incoming requests with a prefix of `foo` will be proxied
614 to `https://localhost:10000/bar`, using the same scheme (HTTP/HTTPS) that the
615 original request used, and using the SSL context of the main webserver. In
616 addition, a header `"x-example"` with the value `"baz"` will be added to the
617 request before it is proxied, using the
618 [`header`](http://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/client/api/Request.html#header%28java.lang.String,%20java.lang.String%29)
619 method.
620
621 #####`:failure-callback-fn`
622
623 This option lets you provide a function to manipulate the response object in case of failure. It must take
624 four arguments, `[req resp proxy-resp failure]`, where `req` is the original
625 [`HttpServletRequest`](http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html),
626 `resp` is an [`HttpServletResponse`](http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html),
627 `proxy-req` a [`Response`](http://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/client/api/Response.html)
628 and `failure` is a [`Throwable`](http://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html) explaining the
629 cause of the problem.
630 `resp` may be modified, the function does not return any value.
631
632 An example with an on-failure function:
633
634 ```clj
635 (defservice foo-service
636 [[:WebserverService add-proxy-route]]
637 (init [this context]
638 (add-proxy-route
639 {:host "localhost"
640 :port 10000
641 :path "/bar"}
642 "/foo"
643 {:failure-callback-fn (fn [req resp proxy-resp failure]
644 (.println (.getWriter resp) (str "Proxying failed: " (.getMessage failure))))})
645 context))
646 ```
647
648 In this example, in case of proxying failure the response body will be augmented by an error message explaining
649 what the cause of the problem was.
650
651 #### `override-webserver-settings!`
652
653 `override-webserver-settings!` is used to override settings in the `webserver`
654 section of the webserver service's config file. This function will accept one
655 argument, `[overrides]`. `overrides` is a map which should contain a
656 key/value pair for each setting to be overridden. The name of the setting to
657 override should be expressed as a Clojure keyword. For any setting expressed in
658 the service config which is not overridden, the setting value from the config
659 will be used.
660
661 For example, the webserver config may contain:
662
663 ```
664 webserver {
665 ssl-host: 0.0.0.0
666 ssl-port: 9001
667 ssl-cert: mycert.pem
668 ssl-key: mykey.pem
669 ssl-ca-cert: myca.pem
670 }
671 ```
672
673 Overrides may be supplied from the service using code like the following:
674
675 ```clj
676 (defservice foo-service
677 [[:WebserverService override-webserver-settings!]]
678 (init [this context]
679 (override-webserver-settings!
680 {:ssl-port 9002
681 :ssl-cert "myoverriddencert.pem"
682 :ssl-key "myoverriddenkey.pem"})
683 context))
684 ```
685
686 For this example, the effective settings used during webserver startup would be:
687
688 ```clj
689 {:ssl-host "0.0.0.0"
690 :ssl-port 9002
691 :ssl-cert "myoverriddencert.pem"
692 :ssl-key "myoverriddenkey.pem"
693 :ssl-ca-cert "myca.pem"}
694 ```
695
696 The overridden webserver settings will be considered only at the point the
697 webserver is being started -- during the start lifecycle phase of the
698 webserver service. For this reason, a call to this function must be made
699 during a service's init lifecycle phase in order for the overridden
700 settings to be considered.
701
702 Only one call from a service may be made to this function during application
703 startup.
704
705 If a call is made to this function after webserver startup or after another
706 call has already been made to this function (e.g., from other service),
707 a java.lang.IllegalStateException will be thrown.
708
709 A three argument version is available which takes these parameters: `[server-id overrides]`.
710 `server-id` is the id of the server for which you wish to override the settings. If the
711 two argument version is called, they will be overridden for the default server. The one-argument
712 version of this function will not work in a multiserver set-up if no default server is specified.
713
714 #### `get-registered-endpoints`
715
716 This function returns a map containing information on each URL endpoint
717 registered by the Jetty9 service on the default server. Each key in the map is a URL
718 endpoint, with each value being an array of maps containing information on each handler
719 registered at that URL endpoint. The possible keys appearing in these maps are:
720
721 * `:type`: The type of the registered endpoint. The possible types are `:context`,
722 `:ring`, `:servlet`, `:war`, and `:proxy`. Returned for every endpoint.
723 * `:base-path`: The base-path of a context handler. Returned only for endpoints of
724 type `:context`.
725 * `:context-listeners`: The context listeners for a context handler. Returned only
726 for endpoints of type `:context` that have context listeners.
727 * `:servlet`: The servlet for a servlet handler. Only returned for endpoints of type
728 `:servlet`.
729 * `:war-path`: The local path of the war registered by a war handler. Only returned
730 for endpoints of type `:war`.
731 * `:target-host`: The targeted host of a proxy request. Only returned for endpoints
732 of type `:proxy`.
733 * `:target-port`: The targeted port of a proxy request. Only returned for endpoints
734 of type `:proxy`.
735 * `:target-path`: The targeted prefix of a proxy request. Only returned for endpoints
736 of type `:proxy`.
737
738 The schema for the various types of handler maps can be viewed [here](https://github.com/puppetlabs/trapperkeeper-webserver-jetty9/blob/master/src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj#L71-L96).
739
740 There is also a version that takes one argument, `[server-id]`, which specifies which server
741 for which you want to pull the endpoints. If this parameter is absent, the endpoints will be
742 pulled for the default server. The no-argument version of this function will not work in a
743 multiserver set-up if no default server is specified.
744
745 #### `log-registered-endpoints`
746
747 This function logs the data returned by `get-registered-endpoints` at the info level.
748
749 There is a version of this function that takes a single argument, `[server-id]`. This
750 specifies which server for which you want to log the endpoints. If this is absent,
751 the endpoints registered on the default server will be logged. The no-argument version
752 of this function will not work in a multiserver set-up if no default server is specified.
753
754 #### `join`
755
756 This function is not recommended for normal use, but is provided for compatibility
757 with the `ring-jetty-adapter`. `ring-jetty-adapter/run-jetty`, by default,
758 calls `join` on the underlying Jetty server instance. This allows your thread
759 to block until Jetty shuts down. This should not be necessary for normal
760 trapperkeeper usage, because trapperkeeper already blocks the main thread and
761 waits for a termination condition before allowing the process to exit. However,
762 if you do need this functionality for some reason, you can simply call `(join)`
763 to cause your thread to wait for the Jetty server to shut down.
764
765 There is another version of this function that takes a single argument, `[server-id]`.
766 This is the id of the server you want to join. If this is not specified, then
767 the default server will be joined. The no-argument version of this function will not
768 work in a multi-server set-up if no default server is specified.
769
770 ### Service lifecycle phases
771
772 The Trapperkeeper service manipulates the Java Jetty code in the following ways during
773 these lifecycle phases.
774
775 #### `init`
776
777 A `ContextHandlerCollection` is created during the `init` lifecycle which allows for
778 consumers to use the `add-*-handler` and `add-proxy-route` functions,
779 but the Jetty server itself has not started yet. This allows the service
780 consumer to setup SSL keys and perform other operations needed before the server is started.
781
782 #### `start`
783
784 In the start lifecycle phase the Jetty server object is created, the `ContextHandlerCollection` is added to it, and the server is then started. Adding handlers
785 after this phase should still work fine, but it is recommended that handlers be added
786 during the consuming service's `init` phase.
787
788 ## Webrouting Service
789
790 This project provides a secondary Webrouting Service, which in many cases
791 is preferable for use over the Webserver Service. Documentation is available for it
792 [here](doc/webrouting-service.md).
793
794 ## TrapperKeeper Webserver Service Test Utils
795
796 This project provides some utility code for testing. Documentation on these test utils
797 is available [here](doc/test-utils.md).
798
799 ## Support
800
801 We use the [Trapperkeeper project on JIRA](https://tickets.puppetlabs.com/browse/TK)
802 for tickets on the Trapperkeeper Webserver Service, although Github issues are
803 welcome too.
0 # -*- Makefile -*-
1 # This file was generated by the i18n leiningen plugin
2 # Do not edit this file; it will be overwritten the next time you run
3 # lein i18n init
4 #
5
6 # The locale in which our messages are written, and for which we therefore
7 # have messages without any further effort
8 MESSAGE_LOCALE=en
9
10 # The name of the package into which the translations bundle will be placed
11 BUNDLE=puppetlabs.trapperkeeper_webserver_jetty9
12 # The list of names of packages covered by the translation bundle;
13 # by default it contains a single package - the same where the translations
14 # bundle itself is placed - but this can be overridden - preferably in
15 # the top level Makefile
16 PACKAGES?=$(BUNDLE)
17 LOCALES=$(basename $(notdir $(wildcard locales/*.po)))
18 BUNDLE_DIR=$(subst .,/,$(BUNDLE))
19 BUNDLE_FILES=$(patsubst %,resources/$(BUNDLE_DIR)/Messages_%.class,$(MESSAGE_LOCALE) $(LOCALES))
20 FIND_SOURCES=find src -name \*.clj
21 # xgettext before 0.19 does not understand --add-location=file. Even CentOS
22 # 7 ships with an older gettext. We will therefore generate full location
23 # info on those systems, and only file names where xgettext supports it
24 LOC_OPT=$(shell xgettext --add-location=file -f - </dev/null >/dev/null 2>&1 && echo --add-location=file || echo --add-location)
25
26 LOCALES_CLJ=resources/locales.clj
27 define LOCALES_CLJ_CONTENTS
28 {
29 :locales #{$(patsubst %,"%",$(MESSAGE_LOCALE) $(LOCALES))}
30 :packages [$(patsubst %,"%",$(PACKAGES))]
31 :bundle $(patsubst %,"%",$(BUNDLE).Messages)
32 }
33 endef
34 export LOCALES_CLJ_CONTENTS
35
36
37 i18n: update-pot msgfmt
38
39 # Update locales/messages.pot
40 update-pot: locales/messages.pot
41
42 locales/messages.pot: $(shell $(FIND_SOURCES)) | locales
43 @tmp=$$(mktemp $@.tmp.XXXX); \
44 $(FIND_SOURCES) \
45 | xgettext --from-code=UTF-8 --language=lisp \
46 --copyright-holder='Puppet <docs@puppet.com>' \
47 --package-name="$(BUNDLE)" \
48 --package-version="$(BUNDLE_VERSION)" \
49 --msgid-bugs-address="docs@puppet.com" \
50 -k \
51 -kmark:1 -ki18n/mark:1 \
52 -ktrs:1 -ki18n/trs:1 \
53 -ktru:1 -ki18n/tru:1 \
54 -ktrun:1,2 -ki18n/trun:1,2 \
55 -ktrsn:1,2 -ki18n/trsn:1,2 \
56 $(LOC_OPT) \
57 --add-comments --sort-by-file \
58 -o $$tmp -f -; \
59 sed -i.bak -e 's/charset=CHARSET/charset=UTF-8/' $$tmp; \
60 sed -i.bak -e 's/POT-Creation-Date: [^\\]*/POT-Creation-Date: /' $$tmp; \
61 rm -f $$tmp.bak; \
62 if ! diff -q -I POT-Creation-Date $$tmp $@ >/dev/null 2>&1; then \
63 mv $$tmp $@; \
64 else \
65 rm $$tmp; touch $@; \
66 fi
67
68 # Run msgfmt over all .po files to generate Java resource bundles
69 # and create the locales.clj file
70 msgfmt: $(BUNDLE_FILES) $(LOCALES_CLJ)
71
72 # force rebuild of locales.clj if its contents is not the
73 # the desired one
74 ifneq ($(shell cat $(LOCALES_CLJ) 2> /dev/null),$(shell echo '$(subst ','\'',$(LOCALES_CLJ_CONTENTS))'))
75 .PHONY: $(LOCALES_CLJ)
76 endif
77 $(LOCALES_CLJ): | resources
78 @echo "Writing $@"
79 @echo "$$LOCALES_CLJ_CONTENTS" > $@
80
81 resources/$(BUNDLE_DIR)/Messages_%.class: locales/%.po | resources
82 msgfmt --java2 -d resources -r $(BUNDLE).Messages -l $(*F) $<
83
84 resources/$(BUNDLE_DIR)/Messages_$(MESSAGE_LOCALE).class: locales/messages.pot | resources
85 msgfmt --java2 -d resources -r $(BUNDLE).Messages -l $(MESSAGE_LOCALE) $<
86
87 # Translators use this when they update translations; this copies any
88 # changes in the pot file into their language-specific po file
89 locales/%.po: locales/messages.pot
90 @if [ -f $@ ]; then \
91 msgmerge -U $@ $< && touch $@; \
92 else \
93 touch $@ && msginit --no-translator -l $(*F) -o $@ -i $<; \
94 fi
95
96 resources locales:
97 @mkdir $@
98
99 help:
100 $(info $(HELP))
101 @echo
102
103 .PHONY: help
104
105 define HELP
106 This Makefile assists in handling i18n related tasks during development. Files
107 that need to be checked into source control are put into the locales/ directory.
108 They are
109
110 locales/messages.pot - the POT file generated by 'make update-pot'
111 locales/$$LANG.po - the translations for $$LANG
112
113 Only the $$LANG.po files should be edited manually; this is usually done by
114 translators.
115
116 You can use the following targets:
117
118 i18n: refresh all the files in locales/ and recompile resources
119 update-pot: extract strings and update locales/messages.pot
120 locales/LANG.po: refresh or create translations for LANG
121 msgfmt: compile the translations into Java classes; this step is
122 needed to make translations available to the Clojure code
123 and produces Java class files in resources/
124 endef
125 # @todo lutter 2015-04-20: for projects that use libraries with their own
126 # translation, we need to combine all their translations into one big po
127 # file and then run msgfmt over that so that we only have to deal with one
128 # resource bundle
0 -----BEGIN CERTIFICATE-----
1 MIID3zCCAsegAwIBAgIBAjANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290
2 IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs
3 ZSBPcmcsIExMQzAeFw0xNDA0MDgwMTI1MzdaFw0zNDA0MDMwMTI1MzdaMH4xJDAi
4 BgNVBAMTG0ludGVybWVkaWF0ZSBDQSAobWFzdGVyLWNhKTEfMB0GCSqGSIb3DQEJ
5 ARYQdGVzdEBleGFtcGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEa
6 MBgGA1UECxMRU2VydmVyIE9wZXJhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IB
7 DwAwggEKAoIBAQDTgKLGBkExFRXrQJn/lHE4XHkN2nXwJpS+y8bWqHiBdq5eZ8D2
8 UAILOBaALeQN/1d1J4yrh6w/YK+gRtCLn+CslR+9NW4AgShALi+r26DK9ZRk4F7V
9 Dk4yEjNpmTyVRyP8w7iZwasZdyK04xAhj+yEInz29SLxmh1jddts/rjqLMZW/s0S
10 T+E9XSEDYNVprC5VuYutUuHKah7AYSp07FHNsqDg+y+vCRezSqbrHrGpTwMupVmD
11 2ObsSJntghsLzPwjSGhbo6e8C/TDwrPtm6az9TPKbsUrqjdvyZcSfc5Q6OgExNhg
12 zWQkk5PqFOESsQSBfOOn2eqfqBXHUnH9PCNTAgMBAAGjgZwwgZkweQYDVR0jBHIw
13 cIAUFq+AJeP66ki/kTNmAf1R7yRnTGOhTaRLMEkxEDAOBgNVBAMMB1Jvb3QgQ0Ex
14 GjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQKDBBFeGFtcGxlIE9y
15 ZywgTExDggkAsbkEcvsRJ+MwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYw
16 DQYJKoZIhvcNAQEFBQADggEBAC4keJ+jeGh7/EWwsCKollYW7H4aSjPu/Ufe38hH
17 pEER9FyCqJ0jo+MabOx8l1F5ySNWngB0qbJuA/kiV2gJ1bQ+mE2TN88x6Sz12eol
18 ifhFU0PazGdpNQRhpQxbwJ7tFC3Z8WrHEcVqP9iicNWqSI/QkqXsCk4Zyezpx28W
19 sqHylf1CiBOU45FJdDXRg80mk6WOpNZR8HIUdqQLQDXz0FfXeFKmVteatmc/yrGG
20 5iHzMkH4Vz5laBjin9s1p+O8Z7+cWtJNWfXaULAEecZQ6CZ3V1OVOjhsrL28iF7C
21 kx01rSrsxaFclDalJVmKmO2spHvNmQTlWD6jm5d94WaRyXU=
22 -----END CERTIFICATE-----
23 -----BEGIN CERTIFICATE-----
24 MIIDZTCCAk2gAwIBAgIJALG5BHL7ESfjMA0GCSqGSIb3DQEBBQUAMEkxEDAOBgNV
25 BAMMB1Jvb3QgQ0ExGjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQK
26 DBBFeGFtcGxlIE9yZywgTExDMB4XDTE0MDQwODAxMjUzNloXDTM0MDQwMzAxMjUz
27 NlowSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv
28 bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEMwggEiMA0GCSqGSIb3DQEBAQUA
29 A4IBDwAwggEKAoIBAQDFDXbR+00AwXM+HuMIpw8eVWBzQWBqDCYkX3IvYRGj+w9y
30 7AitrN+J0MZE3pbaRvlH5wU7MShFOmT0k/B/wrylW4W5G/iAtd2ZnXicBPrA9zDU
31 eHJftQxR7+Qjmsc1BqVf43PUlQITpn1APgXDzPJdk9XbRWEsIycuXkwTXzVND0U5
32 z3dGS/oh9yMim0DnF2oQ+gTFA9n17xOD5hBN80U3fn4DXtcFGbtXOj6zBHsxgLCi
33 leif2AB1oAaZ0lqkwk6Se0rFd3zafYLDAwCPCWlZSfkQ0C/W7WYx07PDRxSYs1H6
34 Viz2uHwqzyD6elxvJBGcrLdvDqTLL+w0ag3yMPWbAgMBAAGjUDBOMB0GA1UdDgQW
35 BBQWr4Al4/rqSL+RM2YB/VHvJGdMYzAfBgNVHSMEGDAWgBQWr4Al4/rqSL+RM2YB
36 /VHvJGdMYzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQC/sFnu1TIr
37 L6HhTft5aUaeLuO/329cDUHxlUppGRYrctkZvYK4b8TBi2BD+tcwRKS1kh4nrQhr
38 xaBO+oUmyJeNwEPk40trzusV9N9tfqw8drBBXEVZGxrYRYovq/RqLfUQ224EF3z0
39 r74dAWL0R80PvVzeJfUsUw0KYgskfLzP5QSW1rrJnutfYP95EMV4yWyrNqnDko3M
40 v7XENh0TMEolMxPZ+X3TqT6Q0j4aM8njswObyeABslt+nC6nLfgBvgDaSvEULPL6
41 u5aWNxp9WudGqGBvHoR6OXdZDRCzWSz52jnvXiZE4E0VnqsWxCmjDGECke4TRoMU
42 rtMLavKgCsIe
43 -----END CERTIFICATE-----
44
0 -----BEGIN CERTIFICATE-----
1 MIID3zCCAsegAwIBAgIBAjANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290
2 IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs
3 ZSBPcmcsIExMQzAeFw0xNDA0MDgwMTI1MzdaFw0zNDA0MDMwMTI1MzdaMH4xJDAi
4 BgNVBAMTG0ludGVybWVkaWF0ZSBDQSAobWFzdGVyLWNhKTEfMB0GCSqGSIb3DQEJ
5 ARYQdGVzdEBleGFtcGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEa
6 MBgGA1UECxMRU2VydmVyIE9wZXJhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IB
7 DwAwggEKAoIBAQDTgKLGBkExFRXrQJn/lHE4XHkN2nXwJpS+y8bWqHiBdq5eZ8D2
8 UAILOBaALeQN/1d1J4yrh6w/YK+gRtCLn+CslR+9NW4AgShALi+r26DK9ZRk4F7V
9 Dk4yEjNpmTyVRyP8w7iZwasZdyK04xAhj+yEInz29SLxmh1jddts/rjqLMZW/s0S
10 T+E9XSEDYNVprC5VuYutUuHKah7AYSp07FHNsqDg+y+vCRezSqbrHrGpTwMupVmD
11 2ObsSJntghsLzPwjSGhbo6e8C/TDwrPtm6az9TPKbsUrqjdvyZcSfc5Q6OgExNhg
12 zWQkk5PqFOESsQSBfOOn2eqfqBXHUnH9PCNTAgMBAAGjgZwwgZkweQYDVR0jBHIw
13 cIAUFq+AJeP66ki/kTNmAf1R7yRnTGOhTaRLMEkxEDAOBgNVBAMMB1Jvb3QgQ0Ex
14 GjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQKDBBFeGFtcGxlIE9y
15 ZywgTExDggkAsbkEcvsRJ+MwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYw
16 DQYJKoZIhvcNAQEFBQADggEBAC4keJ+jeGh7/EWwsCKollYW7H4aSjPu/Ufe38hH
17 pEER9FyCqJ0jo+MabOx8l1F5ySNWngB0qbJuA/kiV2gJ1bQ+mE2TN88x6Sz12eol
18 ifhFU0PazGdpNQRhpQxbwJ7tFC3Z8WrHEcVqP9iicNWqSI/QkqXsCk4Zyezpx28W
19 sqHylf1CiBOU45FJdDXRg80mk6WOpNZR8HIUdqQLQDXz0FfXeFKmVteatmc/yrGG
20 5iHzMkH4Vz5laBjin9s1p+O8Z7+cWtJNWfXaULAEecZQ6CZ3V1OVOjhsrL28iF7C
21 kx01rSrsxaFclDalJVmKmO2spHvNmQTlWD6jm5d94WaRyXU=
22 -----END CERTIFICATE-----
0 -----BEGIN CERTIFICATE-----
1 MIIDZTCCAk2gAwIBAgIJALG5BHL7ESfjMA0GCSqGSIb3DQEBBQUAMEkxEDAOBgNV
2 BAMMB1Jvb3QgQ0ExGjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQK
3 DBBFeGFtcGxlIE9yZywgTExDMB4XDTE0MDQwODAxMjUzNloXDTM0MDQwMzAxMjUz
4 NlowSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv
5 bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEMwggEiMA0GCSqGSIb3DQEBAQUA
6 A4IBDwAwggEKAoIBAQDFDXbR+00AwXM+HuMIpw8eVWBzQWBqDCYkX3IvYRGj+w9y
7 7AitrN+J0MZE3pbaRvlH5wU7MShFOmT0k/B/wrylW4W5G/iAtd2ZnXicBPrA9zDU
8 eHJftQxR7+Qjmsc1BqVf43PUlQITpn1APgXDzPJdk9XbRWEsIycuXkwTXzVND0U5
9 z3dGS/oh9yMim0DnF2oQ+gTFA9n17xOD5hBN80U3fn4DXtcFGbtXOj6zBHsxgLCi
10 leif2AB1oAaZ0lqkwk6Se0rFd3zafYLDAwCPCWlZSfkQ0C/W7WYx07PDRxSYs1H6
11 Viz2uHwqzyD6elxvJBGcrLdvDqTLL+w0ag3yMPWbAgMBAAGjUDBOMB0GA1UdDgQW
12 BBQWr4Al4/rqSL+RM2YB/VHvJGdMYzAfBgNVHSMEGDAWgBQWr4Al4/rqSL+RM2YB
13 /VHvJGdMYzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQC/sFnu1TIr
14 L6HhTft5aUaeLuO/329cDUHxlUppGRYrctkZvYK4b8TBi2BD+tcwRKS1kh4nrQhr
15 xaBO+oUmyJeNwEPk40trzusV9N9tfqw8drBBXEVZGxrYRYovq/RqLfUQ224EF3z0
16 r74dAWL0R80PvVzeJfUsUw0KYgskfLzP5QSW1rrJnutfYP95EMV4yWyrNqnDko3M
17 v7XENh0TMEolMxPZ+X3TqT6Q0j4aM8njswObyeABslt+nC6nLfgBvgDaSvEULPL6
18 u5aWNxp9WudGqGBvHoR6OXdZDRCzWSz52jnvXiZE4E0VnqsWxCmjDGECke4TRoMU
19 rtMLavKgCsIe
20 -----END CERTIFICATE-----
0 -----BEGIN CERTIFICATE-----
1 MIIFMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRQdXBw
2 ZXQgQ0E6IGxvY2FsaG9zdDAeFw0xNDAyMTQxODA5MDdaFw0xOTAyMTQxODA5MDda
3 MB8xHTAbBgNVBAMMFFB1cHBldCBDQTogbG9jYWxob3N0MIICIjANBgkqhkiG9w0B
4 AQEFAAOCAg8AMIICCgKCAgEA5vYnoJ85k6qcUFzWFOr9MN2ZWFlgA6nB0Adfm8Yg
5 ovg963NrauwTcoPqbfGhi53A8RWc5GT5x1OSjxQW/PAGGfGg3aHZuQMClYWsauod
6 CYG6YgG49WGZODCHZ+TbHMN1pNV+6S+tnZbUtfVKsN347XyHCyrymmb/OQNAxPyO
7 /76dDR4dB7kWKP4KMMOgDAnA+WDpD1d9/UfBPVZtw/sTyjeJGEOZqSXQW93PKumJ
8 9/DS4azsUMR1JwJA67yWffoHb6sL7QSAQEfp17gDs9asIzITZPPyHNslmY55zaSW
9 EslzFGqPvB7ugUbqH0rjp2TjQ/9Nw7ZKBxukFB9cBUr7v21D2Of2SHQorzRe9lXO
10 wO3BYEt/viypktDnH42qAeoLHDK1ay9ugByTm4ARj14CjslpkEKflk9t9XwUR3ku
11 Uaj3w+Y5ItZXFBxns1OQpDt3bMhJFemj7MxkZXt8tvWlUMKVmCUqbnG1eDdTF4Vd
12 OovdacMDpvKg438I8MCjaHQf90qIp8MaOjdfSoKBCQi6AP7cpjB+x9xhEuetPYcb
13 /bNjV1OFo4h77XJ+lIH58HBpEG0zkqhtS7JqICIzp3GK7ZG3bhfKKJz0Qr4ISxI0
14 YsYWdmsG38SyMwcomMy4WoLiuQF2QusBSHwBS+p5qQyNkJmXEy8KSQmrrIB/bkiS
15 mgkCAwEAAaN5MHcwNQYJYIZIAYb4QgENBChQdXBwZXQgUnVieS9PcGVuU1NMIElu
16 dGVybmFsIENlcnRpZmljYXRlMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
17 AQH/MB0GA1UdDgQWBBRZy3D0vQGiBu3mnggq8jiEZERoXzANBgkqhkiG9w0BAQsF
18 AAOCAgEAZHcyah146g2vmuQZsGJtljCkWjM1WWjcPoLewdd4FG1YKkAgbGGPVnSS
19 yIS04tj4/Z3eePUlBNe3ZHs7yTQkq3SwWIhRSMJRA8xAjtL0rL7mvJ6PMV+Sujb7
20 +ENTML76+oq1d7XTqAh8mnSJOIc6eMUQqBBYl/5BYa4IzkvkrS4ai4vg0ihKr6pf
21 C8XVINTjxChqK08dEr690sPD/3DwPPVGqG+qIyIv6u5buVLniLWiaq4HOG54i/yH
22 k21W6A5UizRxb5GwVxWfjsHcLr2pvPq/ipRP/sCNBgKdziaGQm+IqeJiI6bQegx3
23 ZF5UX2CaIkZsEM0v2yQ7/t9rSCfpd/eEYMdcWcnSfEf/OOA/ti7jnpapENXturLo
24 6/TNJarHYQlkpEsaRfiiSmomAn3TNOOuqBhWtj0fdy21/fETxVVzPp/CL7EvkUnU
25 26SguZdBkGf22yZViKRq22eDnM/frsoSMTkIbkayDQ8Hx9JVpfPmdJWQyL86etsd
26 uCR5OXJtd7vxRZT15m2cFvdpRW3VZbqFyIwe0NgJUs3FRrjFZdqQiBsvWwQjK8jN
27 A3+IAAz4pk0A4xpQvNwvUzo4wyQB8/PTcmZoPBzeDaJScqSto2r3kvOSqvm1PSc6
28 gneGkbPvQpQRH9HVxzaEEcCrcZYFZXpCkF0ACuB+smgfMVY2Sno=
29 -----END CERTIFICATE-----
0 -----BEGIN CERTIFICATE-----
1 MIIFsTCCA5mgAwIBAgIBBDANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRQdXBw
2 ZXQgQ0E6IGxvY2FsaG9zdDAeFw0xNDA0MjgyMTExMzNaFw0xOTA0MjgyMTExMzNa
3 MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
4 AgoCggIBALlZ0tk6PCNVZcdwS8BYD1g8ZFLJqrrsqdHaaLLBZDXSW9lhrumDUi55
5 cpxkwKwg4E+BZTXliGFQLznS/0r4yOp00diMYfik6ecl0prut/H3RUcm2EXENqun
6 qZeExlSArFy6c6ppzlt8pFb09bwp/nRSZ5DkGqssVWlsmAGPu0qLNrZJLkStFe7H
7 Y56YUlolGm4gJRNjRo4fFxa8o4nclk6kSS/u6GP0omXv7qIhIWsP1plMeOEnhArI
8 oeNX1l9pyb1scW0a4t9x/IBuayF3o4/56KOvysVmVfaWlSkHKYWWpmuh0SeIIhbw
9 Oy/ZizweC1C7GlygTujOciLAGkSU/P975qBbQGftu0actbmDOoLTYOPEXHTy1SUV
10 cSrwLzifg05dwEqlLApwPjdPHSuc3Rk6ZsxnQM6RpJ78DFULLSYzyhdkgg89quga
11 QlFeqqKqyamVyJrpgtf4K0Zi2hlUY29qLDtuTlm+Psaf5hC9ihc4ZFxlmvOCTg+J
12 3VY7GkFSG0XjVMDWdfku8Kmgteugpjrw5/Ox20H9wyxQ21Oo35wOOx60slxfg+YV
13 eYp9gbAVN4czoNyGsjf9JX92jifYtyZyzQfRvWYD0eBVvrPpm5JA0QKNZ/94GW/m
14 sATLFFmGMWpfvHaHqHkylmV1EigZlp5nDFQjID/SLdsFJ3VT6xW/AgMBAAGjggEB
15 MIH+MDUGCWCGSAGG+EIBDQQoUHVwcGV0IFJ1YnkvT3BlblNTTCBJbnRlcm5hbCBD
16 ZXJ0aWZpY2F0ZTBFBgNVHREEPjA8ghlKZXJlbXlzLU1hY0Jvb2stUHJvLmxvY2Fs
17 gglsb2NhbGhvc3SCBnB1cHBldIIMcHVwcGV0LmxvY2FsMA4GA1UdDwEB/wQEAwIF
18 oDAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIw
19 ADAdBgNVHQ4EFgQUWS8hsFK9ol/oZGnV633lUiTP8cAwHwYDVR0jBBgwFoAUWctw
20 9L0Bogbt5p4IKvI4hGREaF8wDQYJKoZIhvcNAQELBQADggIBABO4zPGBGrnsGMtt
21 RlQJrau/1Kr5rWz/TJGS3RQjpdXTVI0z5ebLGamsKDaOCmPpq8SVjCv2M+yuUm2O
22 mf51WN+A2906ITTxPD7QhVGp7WRJDMbcr3g9j3VtijDL7ogZcmEC4cUhHwnVDVMG
23 qTubriSHYlUa5bmoZ5nKRPd1rPwU9/jrZUXesf0SqV1W+p+bUIH4aid7U5nkE67T
24 jvqtoouyI2DtUUE/7LOtkKFxXZSKE3gJ5SGfc0ENZmS6+YjMr5YQkreWLys7Cwem
25 430QHE35TvdgqGYtzit/t05473+CVUb9vhy7bJwLGdG/wCnCmuVGNuGVccxRR+vz
26 RuL9IJ8wsXPdVxdksA+VzUxDnsPXOFQRIPpzqF1bMWYoWZV0IgRzkmsYH/zwi7qy
27 QHsxXWQP0Kflx95Nc5BNZWZjKeBZx41Vk7+82YGS83YNMihOX1iwt/sdt29vrxs9
28 uUXFFKcoKrgKcO8PlqP8wbqluO99nGzw3U8dvXyPpTe0tRpnOIfRiNZlwfXVJbTS
29 ZhbgCX2ro7eaENLAgM98rew2toP8bxgh5E5npRizilOSfARu9aY4hUB1wsY9VYUi
30 NtHNBy6jjg5XsYWkfjm448Ev+MW0hy6Iq+53MM+QsMliK3UueImmV5aJf2z7lrvC
31 6+idiQs4I/GfVleruewU/+EZBX02
32 -----END CERTIFICATE-----
0 -----BEGIN CERTIFICATE-----
1 MIIFnjCCA4agAwIBAgIBAjANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRQdXBw
2 ZXQgQ0E6IGxvY2FsaG9zdDAeFw0xNDAyMTQxODA5MDdaFw0xOTAyMTQxODA5MDda
3 MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
4 AgoCggIBALzn4xcbGhSX65fgSIo+NB4EOYX+zJxbHamv+hbQkhAxqHxUSszDD86+
5 s9ZsN8cvpDIRex1/SfkNRKhgVUHGiUYbol+9RLpgpdHYLJCY0S0kOyBUgWqEcQrA
6 tVDYpwaNC5qMneiZkcNvQ74pnuFOa0/bzbpLyOjYKEQq3BXK32yGLLHseb/PIX1o
7 bqcuYiR6yI7S3wK9hJzSo6BRPSz0cNZNpnXbW/yLRtHbthBtzTifM2MW2jtWuFvg
8 OW1ly9vxwTHCrJv5KxMubVDrqAx+RmmOsn327APO3r6NUr2CzV1vG8CMqLApse6m
9 dKpCZ5UNLm4dwCUe2zWAQ0Q2Eba88O480bH8k/t8NUGXlWt25B8BUE8rklY0jwSw
10 v8Dyjda0moeoxgMMZb+ogPjTY/Ds/h2hgFzy/DUuGrv6kyVxzH0/pOg29HeDoUaK
11 IaqjAsN8h/oYTJ6CiOKAqWfbi8JZRmiEVTekskS5eySsPqPCegfAkfpIlz4EU4FN
12 0A2vORI7epW4mNiJcCLb09v5jogK+DhfeSscDsrYgIF8eAPLw6r9h1am+vXoD6vt
13 tIwZBu15pnCWXdDcKWBNjx+zYU648iZ9V/qFG31uTJaw0z18eFPyTcJN8StTnoGZ
14 zjKI2AyjG5F6ov/S7mnU3I/wv0B5Vq6fPjSvMrsBlbBdI3Pbr3w/AgMBAAGjge8w
15 gewwNQYJYIZIAYb4QgENBChQdXBwZXQgUnVieS9PcGVuU1NMIEludGVybmFsIENl
16 cnRpZmljYXRlMFQGA1UdEQRNMEuCG2Rqcm9vbWJhLnZwbi5wdXBwZXRsYWJzLm5l
17 dIIJbG9jYWxob3N0ggZwdXBwZXSCGXB1cHBldC52cG4ucHVwcGV0bGFicy5uZXQw
18 DgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
19 AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRZy3D0vQGiBu3mnggq8jiEZERoXzAN
20 BgkqhkiG9w0BAQsFAAOCAgEAJwDAK7UKZvLtSJkdprO/Z0qALdUSliO0+I6lsSr/
21 A8SyilfMQuxOoq6uWA6j7uMU4SAHwT14QD9c6BJJiBhWLo6HoynZfYK8Smn1q2Xy
22 vNMnUEUEjcWIgIqEDP98RTUZldgR4aWQkQVHB9XY8g6F0qWzBq64qLWjfrsIUijF
23 Z3Ex1OMU7ZMjHhoKk2J3oBcMau47mqU49C9MoWkBH+0fLLr+lxoa40DPcFr+KzhI
24 BHdTKuKAEJEkiE5QNVWl3+M7psFZzdTd+YFz9Vn/9L3aQr8KCb4oMietp84KM0yR
25 6GwodvIjh/3owsnvNvl28HeZGWQMCNIlG0aWx3JIfCOHSYlXfQ55FCtLRqMflJty
26 M4MqRkPHKykZMZmNoiTtXRSz3vMW8JnIyZPsNtfIGltnpjHd4Y4EEVxdZhlL2wBv
27 YycI6PN1oyhv/9ZcIbSaVY4jEpxx6mrsG8iuaT1YHlO2HOwuqvXJ48WkT1Q6go9k
28 A775N00y4jLJXqWchiuP9Hp7AGYWzoKXFDmQbUyl0jpZsrfoTskb90xp6R7+IO7K
29 6opzTahhO9QeURqc7lkdvwjiYMw7uIiTT6IAKtYeK4f52siiJ/LBWucte5FLCjvq
30 KkdWF7aQOKZq+L7Cs2nJ/qQQzlDYTQLpAbBWJbVAscoYYGPVPdKrwI4UEbKGIsWs
31 wMA=
32 -----END CERTIFICATE-----
0 -----BEGIN CERTIFICATE-----
1 MIID8TCCAtmgAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MSQwIgYDVQQDExtJbnRl
2 cm1lZGlhdGUgQ0EgKG1hc3Rlci1jYSkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhh
3 bXBsZS5vcmcxGTAXBgNVBAoTEEV4YW1wbGUgT3JnLCBMTEMxGjAYBgNVBAsTEVNl
4 cnZlciBPcGVyYXRpb25zMB4XDTE0MDQwODAxMjUzN1oXDTM0MDQwMzAxMjUzN1ow
5 HjEcMBoGA1UEAwwTbWFzdGVyMS5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEB
6 BQADggEPADCCAQoCggEBANtT0Se0OBG+bU3ZbZ2IxiSKNs7ZxDBoyXVeVGvOvEQW
7 56TkHnYdoJ3bn3zLctAoWMggv4DxO0nncmVJYbFoeZo9n7viUQdsO8+hTWVJCjov
8 uZYNO88Q5NE4zP/Pi9IWigOzjNMl959ItGI0Sr/aPZUpsc/V6eEpyY0eREGG6Ixa
9 eeO2z/kU4mqO9CK4VzNxfZQqAi0kJEEp2gQ8Ax0gCXee4gbBF7zvyi6467Q3hJTf
10 413cL0jMIPHbNiyXdLlzjtmkYDL9mjnXbL1W339twBgPzs/ZjDqR4IBK4Fzqakoz
11 WvWbp1aTYkRqSBiNRHtiQleCXG7JU6FDeF/wzXXWkWECAwEAAaOB2TCB1jBbBgNV
12 HSMEVDBSoU2kSzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQLDBFTZXJ2ZXIg
13 T3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IBAjAMBgNVHRMB
14 Af8EAjAAMAsGA1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
15 AwIwPQYDVR0RBDYwNIITbWFzdGVyMS5leGFtcGxlLm9yZ4IHbWFzdGVyMYIGcHVw
16 cGV0ggxwdXBwZXRtYXN0ZXIwDQYJKoZIhvcNAQEFBQADggEBAFUas+1NvtqTsT8X
17 CHiwL/njj7at7V6BsF5yw/MnJ2oEwkJpfsp7J3aB/R1s5bxjtxOJ5fVzED3L0uIf
18 we29p16rdSeINn9D/LShF7SUFIB3GokT/L5gHgYPLGH4itmz+GKul6qBdt0bOydM
19 1CqfKTmMEvH0sicEDRFIxji+dfrS6lPhdDHkdKGJeEWpNuATYmw24NYOIpO+4Bv7
20 oVXn5hoZp5VzbokCzVha1hlsUeG+wp3GnOoN2aaAm3LZNqKLhm5dKoNeRtECFEOu
21 +GViwgc9RG4GN4jNDGU03+z+SMozlt3cc+osIxeOKExiK2dfhJwA9Uj1uvYYnSuy
22 /hHeAt4=
23 -----END CERTIFICATE-----
24 -----BEGIN CERTIFICATE-----
25 MIID3zCCAsegAwIBAgIBAjANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290
26 IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs
27 ZSBPcmcsIExMQzAeFw0xNDA0MDgwMTI1MzdaFw0zNDA0MDMwMTI1MzdaMH4xJDAi
28 BgNVBAMTG0ludGVybWVkaWF0ZSBDQSAobWFzdGVyLWNhKTEfMB0GCSqGSIb3DQEJ
29 ARYQdGVzdEBleGFtcGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEa
30 MBgGA1UECxMRU2VydmVyIE9wZXJhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IB
31 DwAwggEKAoIBAQDTgKLGBkExFRXrQJn/lHE4XHkN2nXwJpS+y8bWqHiBdq5eZ8D2
32 UAILOBaALeQN/1d1J4yrh6w/YK+gRtCLn+CslR+9NW4AgShALi+r26DK9ZRk4F7V
33 Dk4yEjNpmTyVRyP8w7iZwasZdyK04xAhj+yEInz29SLxmh1jddts/rjqLMZW/s0S
34 T+E9XSEDYNVprC5VuYutUuHKah7AYSp07FHNsqDg+y+vCRezSqbrHrGpTwMupVmD
35 2ObsSJntghsLzPwjSGhbo6e8C/TDwrPtm6az9TPKbsUrqjdvyZcSfc5Q6OgExNhg
36 zWQkk5PqFOESsQSBfOOn2eqfqBXHUnH9PCNTAgMBAAGjgZwwgZkweQYDVR0jBHIw
37 cIAUFq+AJeP66ki/kTNmAf1R7yRnTGOhTaRLMEkxEDAOBgNVBAMMB1Jvb3QgQ0Ex
38 GjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQKDBBFeGFtcGxlIE9y
39 ZywgTExDggkAsbkEcvsRJ+MwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYw
40 DQYJKoZIhvcNAQEFBQADggEBAC4keJ+jeGh7/EWwsCKollYW7H4aSjPu/Ufe38hH
41 pEER9FyCqJ0jo+MabOx8l1F5ySNWngB0qbJuA/kiV2gJ1bQ+mE2TN88x6Sz12eol
42 ifhFU0PazGdpNQRhpQxbwJ7tFC3Z8WrHEcVqP9iicNWqSI/QkqXsCk4Zyezpx28W
43 sqHylf1CiBOU45FJdDXRg80mk6WOpNZR8HIUdqQLQDXz0FfXeFKmVteatmc/yrGG
44 5iHzMkH4Vz5laBjin9s1p+O8Z7+cWtJNWfXaULAEecZQ6CZ3V1OVOjhsrL28iF7C
45 kx01rSrsxaFclDalJVmKmO2spHvNmQTlWD6jm5d94WaRyXU=
46 -----END CERTIFICATE-----
47 -----BEGIN CERTIFICATE-----
48 MIIDZTCCAk2gAwIBAgIJALG5BHL7ESfjMA0GCSqGSIb3DQEBBQUAMEkxEDAOBgNV
49 BAMMB1Jvb3QgQ0ExGjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQK
50 DBBFeGFtcGxlIE9yZywgTExDMB4XDTE0MDQwODAxMjUzNloXDTM0MDQwMzAxMjUz
51 NlowSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv
52 bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEMwggEiMA0GCSqGSIb3DQEBAQUA
53 A4IBDwAwggEKAoIBAQDFDXbR+00AwXM+HuMIpw8eVWBzQWBqDCYkX3IvYRGj+w9y
54 7AitrN+J0MZE3pbaRvlH5wU7MShFOmT0k/B/wrylW4W5G/iAtd2ZnXicBPrA9zDU
55 eHJftQxR7+Qjmsc1BqVf43PUlQITpn1APgXDzPJdk9XbRWEsIycuXkwTXzVND0U5
56 z3dGS/oh9yMim0DnF2oQ+gTFA9n17xOD5hBN80U3fn4DXtcFGbtXOj6zBHsxgLCi
57 leif2AB1oAaZ0lqkwk6Se0rFd3zafYLDAwCPCWlZSfkQ0C/W7WYx07PDRxSYs1H6
58 Viz2uHwqzyD6elxvJBGcrLdvDqTLL+w0ag3yMPWbAgMBAAGjUDBOMB0GA1UdDgQW
59 BBQWr4Al4/rqSL+RM2YB/VHvJGdMYzAfBgNVHSMEGDAWgBQWr4Al4/rqSL+RM2YB
60 /VHvJGdMYzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQC/sFnu1TIr
61 L6HhTft5aUaeLuO/329cDUHxlUppGRYrctkZvYK4b8TBi2BD+tcwRKS1kh4nrQhr
62 xaBO+oUmyJeNwEPk40trzusV9N9tfqw8drBBXEVZGxrYRYovq/RqLfUQ224EF3z0
63 r74dAWL0R80PvVzeJfUsUw0KYgskfLzP5QSW1rrJnutfYP95EMV4yWyrNqnDko3M
64 v7XENh0TMEolMxPZ+X3TqT6Q0j4aM8njswObyeABslt+nC6nLfgBvgDaSvEULPL6
65 u5aWNxp9WudGqGBvHoR6OXdZDRCzWSz52jnvXiZE4E0VnqsWxCmjDGECke4TRoMU
66 rtMLavKgCsIe
67 -----END CERTIFICATE-----
68
0 -----BEGIN CERTIFICATE-----
1 MIID8TCCAtmgAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MSQwIgYDVQQDExtJbnRl
2 cm1lZGlhdGUgQ0EgKG1hc3Rlci1jYSkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhh
3 bXBsZS5vcmcxGTAXBgNVBAoTEEV4YW1wbGUgT3JnLCBMTEMxGjAYBgNVBAsTEVNl
4 cnZlciBPcGVyYXRpb25zMB4XDTE0MDQwODAxMjUzN1oXDTM0MDQwMzAxMjUzN1ow
5 HjEcMBoGA1UEAwwTbWFzdGVyMS5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEB
6 BQADggEPADCCAQoCggEBANtT0Se0OBG+bU3ZbZ2IxiSKNs7ZxDBoyXVeVGvOvEQW
7 56TkHnYdoJ3bn3zLctAoWMggv4DxO0nncmVJYbFoeZo9n7viUQdsO8+hTWVJCjov
8 uZYNO88Q5NE4zP/Pi9IWigOzjNMl959ItGI0Sr/aPZUpsc/V6eEpyY0eREGG6Ixa
9 eeO2z/kU4mqO9CK4VzNxfZQqAi0kJEEp2gQ8Ax0gCXee4gbBF7zvyi6467Q3hJTf
10 413cL0jMIPHbNiyXdLlzjtmkYDL9mjnXbL1W339twBgPzs/ZjDqR4IBK4Fzqakoz
11 WvWbp1aTYkRqSBiNRHtiQleCXG7JU6FDeF/wzXXWkWECAwEAAaOB2TCB1jBbBgNV
12 HSMEVDBSoU2kSzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQLDBFTZXJ2ZXIg
13 T3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IBAjAMBgNVHRMB
14 Af8EAjAAMAsGA1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
15 AwIwPQYDVR0RBDYwNIITbWFzdGVyMS5leGFtcGxlLm9yZ4IHbWFzdGVyMYIGcHVw
16 cGV0ggxwdXBwZXRtYXN0ZXIwDQYJKoZIhvcNAQEFBQADggEBAFUas+1NvtqTsT8X
17 CHiwL/njj7at7V6BsF5yw/MnJ2oEwkJpfsp7J3aB/R1s5bxjtxOJ5fVzED3L0uIf
18 we29p16rdSeINn9D/LShF7SUFIB3GokT/L5gHgYPLGH4itmz+GKul6qBdt0bOydM
19 1CqfKTmMEvH0sicEDRFIxji+dfrS6lPhdDHkdKGJeEWpNuATYmw24NYOIpO+4Bv7
20 oVXn5hoZp5VzbokCzVha1hlsUeG+wp3GnOoN2aaAm3LZNqKLhm5dKoNeRtECFEOu
21 +GViwgc9RG4GN4jNDGU03+z+SMozlt3cc+osIxeOKExiK2dfhJwA9Uj1uvYYnSuy
22 /hHeAt4=
23 -----END CERTIFICATE-----
24 -----BEGIN CERTIFICATE-----
25 MIID3zCCAsegAwIBAgIBAjANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290
26 IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs
27 ZSBPcmcsIExMQzAeFw0xNDA0MDgwMTI1MzdaFw0zNDA0MDMwMTI1MzdaMH4xJDAi
28 BgNVBAMTG0ludGVybWVkaWF0ZSBDQSAobWFzdGVyLWNhKTEfMB0GCSqGSIb3DQEJ
29 ARYQdGVzdEBleGFtcGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEa
30 MBgGA1UECxMRU2VydmVyIE9wZXJhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IB
31 DwAwggEKAoIBAQDTgKLGBkExFRXrQJn/lHE4XHkN2nXwJpS+y8bWqHiBdq5eZ8D2
32 UAILOBaALeQN/1d1J4yrh6w/YK+gRtCLn+CslR+9NW4AgShALi+r26DK9ZRk4F7V
33 Dk4yEjNpmTyVRyP8w7iZwasZdyK04xAhj+yEInz29SLxmh1jddts/rjqLMZW/s0S
34 T+E9XSEDYNVprC5VuYutUuHKah7AYSp07FHNsqDg+y+vCRezSqbrHrGpTwMupVmD
35 2ObsSJntghsLzPwjSGhbo6e8C/TDwrPtm6az9TPKbsUrqjdvyZcSfc5Q6OgExNhg
36 zWQkk5PqFOESsQSBfOOn2eqfqBXHUnH9PCNTAgMBAAGjgZwwgZkweQYDVR0jBHIw
37 cIAUFq+AJeP66ki/kTNmAf1R7yRnTGOhTaRLMEkxEDAOBgNVBAMMB1Jvb3QgQ0Ex
38 GjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQKDBBFeGFtcGxlIE9y
39 ZywgTExDggkAsbkEcvsRJ+MwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYw
40 DQYJKoZIhvcNAQEFBQADggEBAC4keJ+jeGh7/EWwsCKollYW7H4aSjPu/Ufe38hH
41 pEER9FyCqJ0jo+MabOx8l1F5ySNWngB0qbJuA/kiV2gJ1bQ+mE2TN88x6Sz12eol
42 ifhFU0PazGdpNQRhpQxbwJ7tFC3Z8WrHEcVqP9iicNWqSI/QkqXsCk4Zyezpx28W
43 sqHylf1CiBOU45FJdDXRg80mk6WOpNZR8HIUdqQLQDXz0FfXeFKmVteatmc/yrGG
44 5iHzMkH4Vz5laBjin9s1p+O8Z7+cWtJNWfXaULAEecZQ6CZ3V1OVOjhsrL28iF7C
45 kx01rSrsxaFclDalJVmKmO2spHvNmQTlWD6jm5d94WaRyXU=
46 -----END CERTIFICATE-----
0 -----BEGIN CERTIFICATE-----
1 MIID8TCCAtmgAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MSQwIgYDVQQDExtJbnRl
2 cm1lZGlhdGUgQ0EgKG1hc3Rlci1jYSkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhh
3 bXBsZS5vcmcxGTAXBgNVBAoTEEV4YW1wbGUgT3JnLCBMTEMxGjAYBgNVBAsTEVNl
4 cnZlciBPcGVyYXRpb25zMB4XDTE0MDQwODAxMjUzN1oXDTM0MDQwMzAxMjUzN1ow
5 HjEcMBoGA1UEAwwTbWFzdGVyMS5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEB
6 BQADggEPADCCAQoCggEBANtT0Se0OBG+bU3ZbZ2IxiSKNs7ZxDBoyXVeVGvOvEQW
7 56TkHnYdoJ3bn3zLctAoWMggv4DxO0nncmVJYbFoeZo9n7viUQdsO8+hTWVJCjov
8 uZYNO88Q5NE4zP/Pi9IWigOzjNMl959ItGI0Sr/aPZUpsc/V6eEpyY0eREGG6Ixa
9 eeO2z/kU4mqO9CK4VzNxfZQqAi0kJEEp2gQ8Ax0gCXee4gbBF7zvyi6467Q3hJTf
10 413cL0jMIPHbNiyXdLlzjtmkYDL9mjnXbL1W339twBgPzs/ZjDqR4IBK4Fzqakoz
11 WvWbp1aTYkRqSBiNRHtiQleCXG7JU6FDeF/wzXXWkWECAwEAAaOB2TCB1jBbBgNV
12 HSMEVDBSoU2kSzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQLDBFTZXJ2ZXIg
13 T3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IBAjAMBgNVHRMB
14 Af8EAjAAMAsGA1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
15 AwIwPQYDVR0RBDYwNIITbWFzdGVyMS5leGFtcGxlLm9yZ4IHbWFzdGVyMYIGcHVw
16 cGV0ggxwdXBwZXRtYXN0ZXIwDQYJKoZIhvcNAQEFBQADggEBAFUas+1NvtqTsT8X
17 CHiwL/njj7at7V6BsF5yw/MnJ2oEwkJpfsp7J3aB/R1s5bxjtxOJ5fVzED3L0uIf
18 we29p16rdSeINn9D/LShF7SUFIB3GokT/L5gHgYPLGH4itmz+GKul6qBdt0bOydM
19 1CqfKTmMEvH0sicEDRFIxji+dfrS6lPhdDHkdKGJeEWpNuATYmw24NYOIpO+4Bv7
20 oVXn5hoZp5VzbokCzVha1hlsUeG+wp3GnOoN2aaAm3LZNqKLhm5dKoNeRtECFEOu
21 +GViwgc9RG4GN4jNDGU03+z+SMozlt3cc+osIxeOKExiK2dfhJwA9Uj1uvYYnSuy
22 /hHeAt4=
23 -----END CERTIFICATE-----
24 -----BEGIN CERTIFICATE-----
25 MIIDZTCCAk2gAwIBAgIJALG5BHL7ESfjMA0GCSqGSIb3DQEBBQUAMEkxEDAOBgNV
26 BAMMB1Jvb3QgQ0ExGjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQK
27 DBBFeGFtcGxlIE9yZywgTExDMB4XDTE0MDQwODAxMjUzNloXDTM0MDQwMzAxMjUz
28 NlowSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv
29 bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEMwggEiMA0GCSqGSIb3DQEBAQUA
30 A4IBDwAwggEKAoIBAQDFDXbR+00AwXM+HuMIpw8eVWBzQWBqDCYkX3IvYRGj+w9y
31 7AitrN+J0MZE3pbaRvlH5wU7MShFOmT0k/B/wrylW4W5G/iAtd2ZnXicBPrA9zDU
32 eHJftQxR7+Qjmsc1BqVf43PUlQITpn1APgXDzPJdk9XbRWEsIycuXkwTXzVND0U5
33 z3dGS/oh9yMim0DnF2oQ+gTFA9n17xOD5hBN80U3fn4DXtcFGbtXOj6zBHsxgLCi
34 leif2AB1oAaZ0lqkwk6Se0rFd3zafYLDAwCPCWlZSfkQ0C/W7WYx07PDRxSYs1H6
35 Viz2uHwqzyD6elxvJBGcrLdvDqTLL+w0ag3yMPWbAgMBAAGjUDBOMB0GA1UdDgQW
36 BBQWr4Al4/rqSL+RM2YB/VHvJGdMYzAfBgNVHSMEGDAWgBQWr4Al4/rqSL+RM2YB
37 /VHvJGdMYzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQC/sFnu1TIr
38 L6HhTft5aUaeLuO/329cDUHxlUppGRYrctkZvYK4b8TBi2BD+tcwRKS1kh4nrQhr
39 xaBO+oUmyJeNwEPk40trzusV9N9tfqw8drBBXEVZGxrYRYovq/RqLfUQ224EF3z0
40 r74dAWL0R80PvVzeJfUsUw0KYgskfLzP5QSW1rrJnutfYP95EMV4yWyrNqnDko3M
41 v7XENh0TMEolMxPZ+X3TqT6Q0j4aM8njswObyeABslt+nC6nLfgBvgDaSvEULPL6
42 u5aWNxp9WudGqGBvHoR6OXdZDRCzWSz52jnvXiZE4E0VnqsWxCmjDGECke4TRoMU
43 rtMLavKgCsIe
44 -----END CERTIFICATE-----
0 -----BEGIN CERTIFICATE-----
1 MIID8TCCAtmgAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MSQwIgYDVQQDExtJbnRl
2 cm1lZGlhdGUgQ0EgKG1hc3Rlci1jYSkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhh
3 bXBsZS5vcmcxGTAXBgNVBAoTEEV4YW1wbGUgT3JnLCBMTEMxGjAYBgNVBAsTEVNl
4 cnZlciBPcGVyYXRpb25zMB4XDTE0MDQwODAxMjUzN1oXDTM0MDQwMzAxMjUzN1ow
5 HjEcMBoGA1UEAwwTbWFzdGVyMS5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEB
6 BQADggEPADCCAQoCggEBANtT0Se0OBG+bU3ZbZ2IxiSKNs7ZxDBoyXVeVGvOvEQW
7 56TkHnYdoJ3bn3zLctAoWMggv4DxO0nncmVJYbFoeZo9n7viUQdsO8+hTWVJCjov
8 uZYNO88Q5NE4zP/Pi9IWigOzjNMl959ItGI0Sr/aPZUpsc/V6eEpyY0eREGG6Ixa
9 eeO2z/kU4mqO9CK4VzNxfZQqAi0kJEEp2gQ8Ax0gCXee4gbBF7zvyi6467Q3hJTf
10 413cL0jMIPHbNiyXdLlzjtmkYDL9mjnXbL1W339twBgPzs/ZjDqR4IBK4Fzqakoz
11 WvWbp1aTYkRqSBiNRHtiQleCXG7JU6FDeF/wzXXWkWECAwEAAaOB2TCB1jBbBgNV
12 HSMEVDBSoU2kSzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQLDBFTZXJ2ZXIg
13 T3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IBAjAMBgNVHRMB
14 Af8EAjAAMAsGA1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
15 AwIwPQYDVR0RBDYwNIITbWFzdGVyMS5leGFtcGxlLm9yZ4IHbWFzdGVyMYIGcHVw
16 cGV0ggxwdXBwZXRtYXN0ZXIwDQYJKoZIhvcNAQEFBQADggEBAFUas+1NvtqTsT8X
17 CHiwL/njj7at7V6BsF5yw/MnJ2oEwkJpfsp7J3aB/R1s5bxjtxOJ5fVzED3L0uIf
18 we29p16rdSeINn9D/LShF7SUFIB3GokT/L5gHgYPLGH4itmz+GKul6qBdt0bOydM
19 1CqfKTmMEvH0sicEDRFIxji+dfrS6lPhdDHkdKGJeEWpNuATYmw24NYOIpO+4Bv7
20 oVXn5hoZp5VzbokCzVha1hlsUeG+wp3GnOoN2aaAm3LZNqKLhm5dKoNeRtECFEOu
21 +GViwgc9RG4GN4jNDGU03+z+SMozlt3cc+osIxeOKExiK2dfhJwA9Uj1uvYYnSuy
22 /hHeAt4=
23 -----END CERTIFICATE-----
0 -----BEGIN CERTIFICATE-----
1 MIICrDCCAZSgAwIBAgIBBjANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRQdXBw
2 ZXQgQ0E6IGxvY2FsaG9zdDAeFw0xNDAyMDUxNzIzMjZaFw0xOTAyMDYxNzIzMjZa
3 MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
4 AQoCggEBAJkwwMlLQPQ0XllNlnvpyvd5zcRwPYLaQGakQatnAkls3K7Lzwm8M1sO
5 cHU2peLmXV1ec/vJF5sg0MPfzTukSryy8s5jXUh3wiXm91SgQCAwWqv/5vG6qTh6
6 lQRc77YCg2vpd+zGoUVNx+Xw4GrXFSpWef1T2cbtgzmOYybLdKsEMqLFWm7w6nKb
7 XbNGJYbzKsp69gsXJbD2j1vdmPm+s01g5XmoWG1kaJw4TqKmcZzJ0E9CHtpzOnw9
8 bIHokhA4ToBEGgvYeyiNxaDwMN0EVneG++1dGf3PjUQT6c51cBN1MIAsHinEcpOB
9 quc0fuP927c7pAL80G3vDix0v3p1v+MCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
10 XVMnaE2UR47MzmUl5aWRMPOrCibJA04lHsO2BKb6R59MTHD26WA+6ED7qLhleNq1
11 1cZJHdxXXSKhJu0NMjAui4TwjXY4afZc62CgyOEqcvYVXtgJu6KMog1Lf8IIeFqh
12 06TIaZHgSHrBeVvOz/4nUFgm1QeKbkp9tu5NvgjlNliH/RnGPT2KkiL2iXU6MAvh
13 SGZMXJF8wzQfSRuSYGtxOunOx392yalH7L2nEQOf5MKN1qgFxT3W7UO4kULhE2cS
14 /BOC68Se5s5PXIMtUeQatOpxvopRObS8tz+ibRyfn0kh30d5llHcWtu2Eo9Ngf0I
15 XEH+t1wG6e/o0z29zKk2Vg==
16 -----END CERTIFICATE-----
0 -----BEGIN X509 CRL-----
1 MIICnDCBhQIBATANBgkqhkiG9w0BAQUFADAfMR0wGwYDVQQDDBRQdXBwZXQgQ0E6
2 IGxvY2FsaG9zdBcNMTQwNDI5MjExNTI3WhcNMTkwNDI4MjExNTI4WjAiMCACAQQX
3 DTE0MDQyOTIxMTUyOFowDDAKBgNVHRUEAwoBAaAOMAwwCgYDVR0UBAMCAQEwDQYJ
4 KoZIhvcNAQEFBQADggIBAEHP3m5UA+lz1mjF2K+2jZ9bgZE15POftI9peXNILia3
5 AHE7OQtyxju/Mt/o5OpPW+0RJso9OrXhEE6T65BgOovBi1VkfxXILIui8n1oF1UK
6 9npunKXwgjP6Y8QbuaLv584QdwjfEFlFp/nJW5IQRKVIgsLsSGIBlZvmELiosR/W
7 er3Ba7Cy/Y8cfMx0t/4QCU63l/Sr/8mgvp3P5RwhljN9ekp5Y3Yz4jdRycJsrq8J
8 aQss8N4BrE/ppbvCdmf5UdkXgivd6cl04aSg1f25yYtEY12fyD8C8VySGOfZrnlh
9 aOhJapWTuKVU/PxdCZfeL+RVufrRWBo4iJCRARKddDeqDWtI62pysjy/2PRvzMRW
10 qudj9gJC3JnsWzvZqTGK6LNwDyH65Uc7cXjq+xxMgDpCYKZrpSmwlKUmpfnzZK/o
11 ckJKFaQzXvF/RJF2Ai5W3HEl4CjRp5y2yqpORsl+FDVBl2hi5n7rCqJ4RibbdtTC
12 pFFUP+na3zv0Z49uFLGQ/a/WHI2KuryMlDTkqltr9xncSpKafyT1UCciRdct3Q01
13 EWmuRcsawSg+nLB7VcLRgcnbbRuHJS2Pn2AZxYAg0SshGeZpl0zIiVzadb1/IY1d
14 MlXJjO6RAj87/yUMC7/xC6IJh3sQymC1Yw8nYOGjnGiMCV68e5izd3eQTj8arTZj
15 -----END X509 CRL-----
0 -----BEGIN X509 CRL-----
1 MIICnDCBhQIBATANBgkqhkiG9w0BAQUFADAfMR0wGwYDVQQDDBRQdXBwZXQgQ0E6
2 IGxvY2FsaG9zdBcNMTQwNDI5MTkyODA1WhcNMTkwNDI4MTkyODA2WjAiMCACAQIX
3 DTE0MDQyOTE5MjgwNlowDDAKBgNVHRUEAwoBAaAOMAwwCgYDVR0UBAMCAQEwDQYJ
4 KoZIhvcNAQEFBQADggIBAKir0kv1+pu8zvclpswbvUHwvJstJIOw0K0LBEGJqEO3
5 wdm1a00aVQLx/QXskNoOaPk6HDrLnX+vp+i3ODvk3Cvr29LJIR/qaefcJ76dGECG
6 PlpS3urvHfP+DglA5lD27lrKJN7VNSMfJGMR+AqC4DsFXN8vBEH7353CDcIP24mu
7 Vw/i0lkmcsYFdwiBVzErrO2PX6B/22itxjGf/pINWM1RepQCCu8Y5iBt30YFEmTi
8 LiMjkoLkOCyyVZNiVJ41qMy1E9bZaq8oUfL6wLeJthNsZDpkRlFY7cTz4WPSLGUx
9 gDkSXJZo7bRZ40bW1NwxfHUYJhpVShOU2cJq6Wku4kh8eyPOyjQTa67dP0xQT7FZ
10 /RO2R3BVLsYAE22VpOQs6hANfmPWKHYTvsrEqTAAYwX6sw356MoOVpal86ztw7qu
11 PM5YRzPs/0B5c7OULWMGP/W1R4muFfF2Bv+58WwP/t/lGM/jVZRKkf7KqtEeRLJ/
12 AWrseB3aW0Pr0k01XqXhU9ccNMOOaki1JEaeoGsxjgWJksFliNq9pzhSOiG/S0d2
13 tsa+gSq6/uSFnz3CySyxQHDCrwZDdupgyV6hXeRRxwsquKqjRkTMML45aI/TKxKf
14 IKkMw4Gt2UJeY6lGp4Fa+xJGFgtjB8Zdx4w96KQvafs/uWbJMdI5HrLfEAMY+MGR
15 -----END X509 CRL-----
0 -----BEGIN X509 CRL-----
1 MIICdzBhAgEBMA0GCSqGSIb3DQEBBQUAMB8xHTAbBgNVBAMMFFB1cHBldCBDQTog
2 bG9jYWxob3N0Fw0xNDAyMTUxODA5MDZaFw0xOTAyMTQxODA5MDdaoA4wDDAKBgNV
3 HRQEAwIBADANBgkqhkiG9w0BAQUFAAOCAgEAA/eLiUmIDKJn3DHXi9yxohtSoWXG
4 ZaShRWb2lcRcYuTNF/HtQejpuIlFOJYAxrGpNGwgvHOb2SMZaftjo7cl341qEAhz
5 P/brdO9TWFjJLQgAuyQ4b2xgEnBwtXyJ89luln0cgCBuZ0IBa0KHRz3WbZOn7PXl
6 qdOjEF2/PqeMudfvN8WoXS/hW0IPegY8Cww8jUtrNq7aXoP5ITxhjCKhN5XyidmV
7 vPf0OqJyigZ3pDG8Pj9g4uroaOiH7bFWGr5uCY5RVhq3lHJExDUTDLlG1Ne7/8QL
8 EaMm6kJ/HtyGXRtGmoE990gWdvi2Ml0o5xZlyFsZCJaOwvGUpXy3doIN+Bu8LttT
9 M3c1mSVXgtsRKijqflEWq83LFw7qnCN1SWA++0EWvA5VU1QC7vXUfcXFizTXnLqx
10 sneXuHB8e3Qb3uaYaQg2DICvUGM6mHkqpltWitQpiKNp5R+4DdZI4ZKyndTgoD0c
11 FFcleOXHs6zyoYgkE8YTtqcALoUfZBtc5eCMMVt1ACpXl+H0Iaih8aLiUvxspK2q
12 cjAhrEepvHBPFW8pnclj9Kh1VNpQrsbA66zw83z2YhyJRZJynlO2eQ9dZjcBoOQ0
13 01fJZST4Ru3ZQyf0O3UNXU0SoPPsZsgPvWxnvIZmXwh5sVLjyIoGLIAZDa8O7lEd
14 v0PeaXGYm8Piq5I=
15 -----END X509 CRL-----
0 -----BEGIN RSA PRIVATE KEY-----
1 MIIJKgIBAAKCAgEA5vYnoJ85k6qcUFzWFOr9MN2ZWFlgA6nB0Adfm8Ygovg963Nr
2 auwTcoPqbfGhi53A8RWc5GT5x1OSjxQW/PAGGfGg3aHZuQMClYWsauodCYG6YgG4
3 9WGZODCHZ+TbHMN1pNV+6S+tnZbUtfVKsN347XyHCyrymmb/OQNAxPyO/76dDR4d
4 B7kWKP4KMMOgDAnA+WDpD1d9/UfBPVZtw/sTyjeJGEOZqSXQW93PKumJ9/DS4azs
5 UMR1JwJA67yWffoHb6sL7QSAQEfp17gDs9asIzITZPPyHNslmY55zaSWEslzFGqP
6 vB7ugUbqH0rjp2TjQ/9Nw7ZKBxukFB9cBUr7v21D2Of2SHQorzRe9lXOwO3BYEt/
7 viypktDnH42qAeoLHDK1ay9ugByTm4ARj14CjslpkEKflk9t9XwUR3kuUaj3w+Y5
8 ItZXFBxns1OQpDt3bMhJFemj7MxkZXt8tvWlUMKVmCUqbnG1eDdTF4VdOovdacMD
9 pvKg438I8MCjaHQf90qIp8MaOjdfSoKBCQi6AP7cpjB+x9xhEuetPYcb/bNjV1OF
10 o4h77XJ+lIH58HBpEG0zkqhtS7JqICIzp3GK7ZG3bhfKKJz0Qr4ISxI0YsYWdmsG
11 38SyMwcomMy4WoLiuQF2QusBSHwBS+p5qQyNkJmXEy8KSQmrrIB/bkiSmgkCAwEA
12 AQKCAgEAxLphcLiPo49MjEs3cyPiPJBVAONIHHaphtfxAU/XFtnabEao9a9WtVFW
13 CwYpszHRWckuFYFJHRa7nLHhDtwoZkrh2kb1nFjLB6+P+JWn3CQrLTYIZMMYbrsv
14 aziNxsda2uebrWaWPMhwMPlaAKNiWG+c289eTFR1CwwRTHlQGNk1DypaUey+ynXx
15 Gi5XkklwnTqF6jJ9N5O6LtQCtU+VQMbjOM7EAUcbXlTmFMhOY+o2xlG0DOv6Whra
16 T7IYgf/J+703cFFIKPjYX433YWT2xRfvWytLTHcCZPTuHVMXHbOIGZjXC8dRIr7T
17 x4nTtg8CYjYB+DW6gqlxrk4z5LJFEd/zVlauBTxIIUcYTZeyM5Ssl6fUMBgyt9f5
18 ULfsxrahNEzeduco+1Ga5IgzIIXM3tZqQKdgZYwltauSBaMp0aagYVCdV0Y6+a5P
19 QKNHkW7PoqyU+VwegDUDYOIxjw0S2C0m4VVzvRws3f1Xvtrq/rxNpt1wO/UcJZYY
20 Qit1IHCJwCHaaW2R1mDBz4HenHAYdtoxBzwzDcECKoNHYyMoBRUTQx9MB8eKgQuH
21 Y22bh2squJAqm7ubTP/Ek8KkY7BIWA6o3WKWfI8V0V0ar7siLv2GCAJzyuBFlIPO
22 n2cmqONZdBlWlgTObTi0PzncGnWk/4HXinMkR2WjwvtQ3MF+xv0CggEBAPO3Ahbb
23 NnLW9OsaiuUpbcpF1IhMIwoaaXo70GlqL7FpHbrKGb4CnKjBZlUnKjwvggfsl7JW
24 hkwxL0P8vKItgEm9Uc8B5W1CRUZtysHCvuc5ROYJhBxwX2pWIcpMMu02WtD6wpp9
25 xb9WymnEA8ztgepSMHrDohLlXXWWC8FqCekAx8LJ9K8yVaJU1CKq2ZZw65aMca2B
26 WPLh+nX3YL8UIAbwUfJDs9V0xE0UhQFni8yv+8Gu8jicL7+Oj0OyCM1QXC1t+96I
27 2hw9qnsBFeCT3Xkft5U8LNumQhWG+Ij2asUpzYaC4iDXWkX3b04NrH7VnwPsnRUj
28 JcHTqx05WgYh1esCggEBAPKakpi3MHal/vykmzT+OLvB5HrXTQaZhoV2lORzxtnR
29 tygwfkWtRbsMeKF3xpeuS5mZcdOIfHjJhY5qCZ3qYA1BOVN+pwMzAuFToLJO8MA4
30 /pF1sDODkzylSdy3kfBsCdCJFG54fwB+6HS553C48RBknAj7ctC3vKwod+YvAEIm
31 VQDvQ16MuGvuiZma4PbbFf3QoJr9yonfBFCEmAy1c4ElviKROgHLY8NluiBJNyYJ
32 MrQnD4HBvlmCzGAGn6uWyzvmJs0t4nSv7xeRL2qaWiFORkc1CSclqiPYuZWoQ6nY
33 ayKO97hFpdABEPfQLHpG1fWzEC0IMfzyHDg8TmOZTtsCggEBAJ8ksvWP4cctvLLF
34 G0u3ON0rqjPyW7MeOiA6bMZZM5mKxYIStKqR9BBycctLDtZMQ4G/KfOncnzQZUfl
35 Apx4T9xXBtPBLjqhWrE5wnuyGozMpcP9FMqpSnUal2gNR/gEVVs/U9IkLPvbxcM4
36 3y5zLTGAx+1yUCQ8qEG/YB/FiYEgJFF7JQ8+NBMTh463t+v4Aq7FOPoOi7HWhIGB
37 ryg3EQ7W4Pvs38KURBb9PjkDj5Nym1gHUF5NBxT0F3MiD4NaZUa5Gg5fmOV2ExvN
38 Qqbqh2Wvo2aM69HmiCKchzCQkHn86Rtb3iOJ3IXxpDn4zdnozrR2TsDduasO+B7W
39 M4XrlE8CggEBAJlV1T4s2rbDKCzqpSaTX6xcWlrB5e877ehBEM5r1s0pXchLDiRf
40 5ejZcw0rNRv9j87fSzBV3cZCKOXgY3+p+VenV+JL9KdzAGkGgFTyy/vSiiPJ7LpC
41 eTcliU+1vsnknzdszLsd8beQfr/4GC4I1mR0EqMiumjtGJT/ZvjX0CP/Mk7K6xvB
42 eXbOZ63sVC/yPS4VRM1xhygpCwMRK0EtFnoULt6OR8mGkeGYVFG/tNmXirKO1aA/
43 ol3U6/Pte9HqFz4es3uPesghws50dzG7qSfP3192R/i0N5s8id/rYAjjvqMzFaMk
44 ci7L3bujmdkXGHiY2qp7uYyUQf3RMAKHjW0CggEAIUQmr1L4Z08JkFrAECdyroVL
45 YLL+SED97h2aQ+Xs1QpU7R8TxbGYJZI5wvUTGfXNcFf5EaEyWvYc2PCtIhvkaVFZ
46 O7rkSLbB46Ou57VufK9LnHAhAxLatYCA52ykTJGvEI+VGu4vTcXSBuQnR1Xc6jOa
47 CrmIUQRrdg9VBCse1WFWVOj4PbCFVEI/aow264E16C5jvQCr/bZSnz/PIW2FMkIr
48 WRlbE5B98oWsxKmVgTFpP7FOHOT+Pww4/i2p+8Wy9JFocUMAKE+sQf2NNA/6Lfk4
49 X9YLExKoFomNFRHVoelW5/qtr5Rx/z+XFMuMhRnH09MJhnpwe4886Sw83bITZQ==
50 -----END RSA PRIVATE KEY-----
0 -----BEGIN RSA PRIVATE KEY-----
1 MIIJJwIBAAKCAgEAuVnS2To8I1Vlx3BLwFgPWDxkUsmquuyp0dpossFkNdJb2WGu
2 6YNSLnlynGTArCDgT4FlNeWIYVAvOdL/SvjI6nTR2Ixh+KTp5yXSmu638fdFRybY
3 RcQ2q6epl4TGVICsXLpzqmnOW3ykVvT1vCn+dFJnkOQaqyxVaWyYAY+7Sos2tkku
4 RK0V7sdjnphSWiUabiAlE2NGjh8XFryjidyWTqRJL+7oY/SiZe/uoiEhaw/WmUx4
5 4SeECsih41fWX2nJvWxxbRri33H8gG5rIXejj/noo6/KxWZV9paVKQcphZama6HR
6 J4giFvA7L9mLPB4LULsaXKBO6M5yIsAaRJT8/3vmoFtAZ+27Rpy1uYM6gtNg48Rc
7 dPLVJRVxKvAvOJ+DTl3ASqUsCnA+N08dK5zdGTpmzGdAzpGknvwMVQstJjPKF2SC
8 Dz2q6BpCUV6qoqrJqZXImumC1/grRmLaGVRjb2osO25OWb4+xp/mEL2KFzhkXGWa
9 84JOD4ndVjsaQVIbReNUwNZ1+S7wqaC166CmOvDn87HbQf3DLFDbU6jfnA47HrSy
10 XF+D5hV5in2BsBU3hzOg3IayN/0lf3aOJ9i3JnLNB9G9ZgPR4FW+s+mbkkDRAo1n
11 /3gZb+awBMsUWYYxal+8doeoeTKWZXUSKBmWnmcMVCMgP9It2wUndVPrFb8CAwEA
12 AQKCAgA8JDIvPB8hU+dXhE+AFNK9zbK7ziXwyzP7HMRWJDww3pXg/jo0GOFEpTGU
13 H3kJIQ3HnnPJvjW3Zb49JAKkP/9pXAhvHHX5qQEgeHxSu1zCTXqUML2CPb2Diz6T
14 JIj6CFplKDa5t+U2eEYW1RsbOAERm19xeyJQIm2AbLUR5KJf+LYDBdHWhNcWCGqB
15 nmkySNG/o/yDwvW0zc+/F+x0oje/Qr1gqUOMk2dSbjzfLKLcS2JVkaOzYxAMM7eb
16 vfeYNAo6xYI0ZcHxwNmujqWCUYCoCe37luHTlXYVMh0qF+HYL97GE9Z63kg5ay+5
17 QfxUwGbphhxyq62PCtWsAutDD5jbe+q5xA8WXt49wbUVfRiIItoST1wcA/yk0y3Y
18 ra0fHvtjaFbj5XO8oyzMbmuh+gPxH/2RHKzbx/Mqd1Z9fp3oFPoUt7fRYBXdz8a/
19 hXiYpQPO51nGMBRyXSbkvCQudFU9jUG0onwGuC9zuee8EmKnyii3NCr3lI6cM8Dm
20 jOvlqIbsrdee3ToIk+mUhy/fm+rSFK5mxT2j4zTQdTai6+1+UMbfq4nDVkxZzhzI
21 WqJfjMapPwiXYEhroGBnPR/hIJGqEbonubKZNGfpc9igiwUYix+xbqhh+edaPsBl
22 iYoEuf3VaOMLEMmShGdCsiyA3nITxx1e7GrdFdFyO5Wxs4GgGQKCAQEA2mA/dO1P
23 6D1JyhpvQdHlNqau87Zjp43pcSRUCpVlNzCsp5WAALQoeoXkBpXvsBydhygs2UUE
24 vrTaNYJ9/g/+PowSunybQjogF0LXATHSz2SYSBOxOQSjRYwnQlGhAruq6i95R3ws
25 BRwmf6RR1+FBqtsiRsBZJnfJ0UCLFLK7lrGK674NYiRsp1UbBibGWal/jgNnB0Qb
26 qxc/NZV/CxVQ9JT8uUostJgEyKi8Ka/YPgoNFApA+1KqY9eYGc5oRGxYxDawMxvt
27 4FjQiaRBfWniGNdNrrRO0FbP5YoDQvsxddX4FSj4AZ69H80xA9/qZlyaqg1nAw9r
28 /hwmRl9TrAi5XQKCAQEA2Uj2Kx1cTgFL8+g8+YzcaatZSWPyf43E6FiXPiKGrzxe
29 IWEFv2co9t7XZ2T0nNQwSpvx6FI+a3Qp6t8QIwTtFCrYLyAOoXYgrhVP9nHKxPYl
30 MHgKFkJkl4sy+m6an2dvVUul/bzLsM1xEzOTmuEWmXXN8YO/bmLZemJIUSwIaud1
31 ZivZqqZZ1gNM0NkmK7DOBypIjnahytHsh0HtrIIksaVCtb8kaS69skssHWWKwX5g
32 O2xlPXAksoArXJ17y/kaIljtmQp7mwnZ5087OuaJYJcFDfewMcQwMGd0xw2h5N3/
33 89IhpeuliZjhgNgPRJrctPnxHNFg0Nk1XWcY46ntywKCAQBnXlPrV0IR3qEFJ8ou
34 T9q+Kdx6xIUblRNBWT7m25zTqRixIxU6QA1BIgu0Clkw9fqjNaM1HXSHiTgZSDG3
35 h36DwO5ElCMyEC2MlTkM+baeMTHcPGYRZV6b1yHmRJmAn7DhtaMk8WQs7wxSM0gC
36 dkANTjlFYFtS2DUR9glfvNMLG/N0b7wKDs/XzXNDUCtn2dHlOTsVt2mZbSGgjalb
37 Z7WwcK3IT5Il0ifBjA21deVYSI20RL5JuPGq3SzEWm4EpXzHNFPnfXr2TVQ5MyVE
38 5k/+DjxZTERaXh1+u/ubQyhAbQ5HheMPcUJ4wTpIaT+dQIx9nmZ6jlxCJrT/brQ+
39 pMqJAoIBABrJ9A+8vvSfFE4uA9aAl2wvxAJYYD15rR04Tu1KNHDGcJSM8bh3b4WA
40 U+5bdA0h3BJWx6xs92UoHULn3YVzxgcGgUDOIv+lIMJVvlYUEXvXHR6srhGwfdZx
41 Qwe9OzML/Z32hbCS5koWCirj4P7nYXHqJEnyhFeHuGhuVZwsYZ1MjBzcqylu+QR5
42 w668Fwir87rOa8OkvK3U0+SZLERohz2fsmnV9xdAvAKPYhD9w+23NwYchx7cBKo9
43 QxtYDztGqwIxFJoZwMOMo2DxU8wfQDC4bdcbAo4gMhDFsJAaDiu5cyUMczmRpAci
44 4iqQeNFshmmJp0B2UAlvvjSV0WvAN6ECggEARkaHLnwX7BomgUqgwQcoZyLjTzf/
45 jpXWaUuBTwJTBOqFPmUQOLrSFLDu0hjk++7SNmIoi1L++BE8vqNI8m0zleCNVofd
46 IQR+XdNjKn7/oDfP0FDVi3ZspEStYOXBBtzG9JMhnUGWzPbMpY5+0igW1jdT3Xz4
47 Azfu7aO3QnhGNg4KuyX5h8sGO0wKeQJhgpQ665X+LY+vBVmf9WWG7WC86NzcEgaw
48 twYp5Ydq3NEpedvxhSR3pwb3UBYIbHlJUwT8fdWPMssp4C2ZpqyTT8/zu5qlKi2D
49 MY9gHIlshA7lqNaEAifo5NdDTHceJgC/LtGU4nqcKTB3HL1afcQtyIK0bQ==
50 -----END RSA PRIVATE KEY-----
0 -----BEGIN RSA PRIVATE KEY-----
1 MIIJKAIBAAKCAgEAvOfjFxsaFJfrl+BIij40HgQ5hf7MnFsdqa/6FtCSEDGofFRK
2 zMMPzr6z1mw3xy+kMhF7HX9J+Q1EqGBVQcaJRhuiX71EumCl0dgskJjRLSQ7IFSB
3 aoRxCsC1UNinBo0Lmoyd6JmRw29Dvime4U5rT9vNukvI6NgoRCrcFcrfbIYssex5
4 v88hfWhupy5iJHrIjtLfAr2EnNKjoFE9LPRw1k2mddtb/ItG0du2EG3NOJ8zYxba
5 O1a4W+A5bWXL2/HBMcKsm/krEy5tUOuoDH5GaY6yffbsA87evo1SvYLNXW8bwIyo
6 sCmx7qZ0qkJnlQ0ubh3AJR7bNYBDRDYRtrzw7jzRsfyT+3w1QZeVa3bkHwFQTyuS
7 VjSPBLC/wPKN1rSah6jGAwxlv6iA+NNj8Oz+HaGAXPL8NS4au/qTJXHMfT+k6Db0
8 d4OhRoohqqMCw3yH+hhMnoKI4oCpZ9uLwllGaIRVN6SyRLl7JKw+o8J6B8CR+kiX
9 PgRTgU3QDa85Ejt6lbiY2IlwItvT2/mOiAr4OF95KxwOytiAgXx4A8vDqv2HVqb6
10 9egPq+20jBkG7XmmcJZd0NwpYE2PH7NhTrjyJn1X+oUbfW5MlrDTPXx4U/JNwk3x
11 K1OegZnOMojYDKMbkXqi/9LuadTcj/C/QHlWrp8+NK8yuwGVsF0jc9uvfD8CAwEA
12 AQKCAgEAr5bHkfGiI2Q3G9vg8YbyQLhik7eMjwVupAyr5MsICb9uwepEAOKLbfv7
13 A6NhkWcqM1PmYTuxEauQlwW8GcCmVqFXI7C1EpzFZTGP8vPo8xHLV7jU9qKWxIzt
14 vHE1h7RRBd4Q5WThhYyFplvfj8OpofhI2RKadDx/6SUBn8wMMz7gip2paW3pzjzl
15 JcbKeOgcRg2iN1Tb0D1G1LzOpVutCrXwtXopnawELwsPx2OYrznjtQZH4YIxKU1Z
16 c+N8QzwK/OrcMLrBnDm6aM4zTTGO141JQibjqIKArxSDxR2xMFkXrbnRDrYi6xaU
17 OLIyv+wZrUdAFAEDd056uAueGYK0WtLq41ipdBFsqkOXLrdRsyp/t7lG58bmtrzA
18 ZniyYAMFjfpzHKlx69nq4KJeMAUKoCscc9CmWmX+Ej5VvFa1x2xw9qWBkBHdWjEa
19 QaF2NvdE7c9TspwfFu7IZ0jnvxN6yvc2RRSMjZnIJ3jW97wp2+PG6G0uhoYXWSxL
20 cGqoKAnpROAaBBB8n3HQ4kPhNZqiV+xqSIuDBFSDohiSdHAqBawJfeA3ssh0Y7nZ
21 WvGxr+iBB9JF2dmtEV0ySTR+bsdMb5IuPyXmXnZS33DvnvSHL6Ap0UwS2wAyzzew
22 VEyE9+9wUqgnPQ0mgo95ARPBfuPestwNhHujQdHLgZe3t/eq6GECggEBAPQFQK/j
23 e994Zp4gPXJMsLHCDtstwZivS/6CEdR96jMPwGgahjHjrSP0ynCyC4uY7PAAu4A1
24 0vjBZ0nLvJY0dVvW1f3GIsQM37jZf8eHUEHXsqGL7y3Nx0bHB3KbNHLw4ZhhsZ+7
25 eCcTT/ExHmshPr8NtsRQdPBYG4agkliJSzNWeQgOLZM8CyDISoCWQcvAbhE5pbOJ
26 NGmbQecFurgBBGAyb3IBvZWdkT/85OtucDh1CVFZgpG6FmpNbVSOnrha4N7KtSjN
27 OXjXvHN0b3GJe62Mjn+yLBOcbvCSkWWnnawV5NuvmH7Oe5FYsTbTBmgH4445T1q/
28 wCFTydMqf3vhVRECggEBAMYt+dN7yMKriMLUDpPPFO607OMhiv+r35AKefTHMHML
29 pnx2OKiBSQUWt2v9z7uTsHcmNzXSccXUv0AF/DxHZebWZmm+VWgGOGMVaml6plzM
30 3A+hjsRcjjFaF1lmHy9+skuH0nmiO5hAkQeG5tZVaQ/Cc1k23RiL4WpTZ8v/4JLt
31 9dhMFZrcTugmCgDyN3aivoO+i1JtX7PcpLbxFv5+e9eGvpQ3FpwEYLT/+DA2DH52
32 /X9DlexfMoM8j0fs6NqiMdPUxRWuepIgTfrbqfsfcPABpieMGmXGtjFUsV4vpUvr
33 M+ZN8KvNVCsznjC+jDyCthfYtHWE7CeWmEnhOHTSfE8CggEAa6PJhgzVvpzQv12/
34 XSUBKFhOz1YeuOhSoGDl1pL4dS+0kvdoTKd+34aCqjWPrDN4COJ50zNq7bn6gu3x
35 MVzQjAN3f6sf+NUo9tRSbkR9HZ41ONeOWOkVx13SJjbaav1gtiQaAzjh5nK5Z85f
36 +ae/ku1Muso22zIyai94fr+JQYsadngymGj7C6nuW0xsl6E5rDV+p3SVfyQybOL1
37 G2evc3Or/2FPLKlFwjEfFc8wh2bxBkZyty+b5aZj3NHQp8fGu+A1C1uDx496nH83
38 DaE0wjhnP2Lr2Ha/5TTyGCJZBejefB24Ke+RSGsUOPfbMpaQRVN4crJ04P6h35k2
39 hQG/0QKCAQADdmAsAriiNg8AoGXUzURnWz/cRATCrMUOJjC1RxmgmO6CtCoPP5r/
40 /MKdn2SWuWDW5BMI3LFiLHJe8vvSLcko/EvzwwCI/brUeFZQm3T2oBmkKEVvRtKx
41 KArKZA9dbBA/Y5MYzu3Nnisqf3/e9MUOIm6Te3Lnb+IzUlu447KPvpqR+dpSx1CV
42 m7yHAbRYXUWI1bZnbUPDx7IVBCdLsPgG7vK7ci7x8N2jq+kxJnCXcQrCw3KGG6+t
43 PUyfjBMRZs4KDmiXFWJM1UWngVj56zW068J0ZG09o/gg6oLiy2BO8EAK4Qe4aLD0
44 xEUaQun+UKZPylh0ySq7ElV8zPOIjvjfAoIBAC3D6xkfricyFdLNtey6DPvFt00V
45 hJBr1r3700hGGyROaLxsCKUJ+qvsSnkplR63MgCNuX312qXwlnQjrZdP3YprhhmF
46 14HRm61xbI4wviFPjzO04OsIkxLGq/Ir2QPsg7RYIubtUfbblxndz5oz38lGGQQ6
47 PNroPKq7exouCYXTfslFxf7MHs6pF3AjUN3H8WwvkEtOSNEaln6q59riY7QBBQxT
48 GMx3RyIIPnw/XRwl4nKFuIpnQbph+gqA5HXysbnht60YbsxUQSk9pBqSU88uxWA1
49 A7OasL2hCO2dRkwXncUXkOPQwaNn6tNCCYR8Sp0EJC6WN7pc1fqC8cPCk+g=
50 -----END RSA PRIVATE KEY-----
0 -----BEGIN PRIVATE KEY-----
1 MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDbU9EntDgRvm1N
2 2W2diMYkijbO2cQwaMl1XlRrzrxEFuek5B52HaCd2598y3LQKFjIIL+A8TtJ53Jl
3 SWGxaHmaPZ+74lEHbDvPoU1lSQo6L7mWDTvPEOTROMz/z4vSFooDs4zTJfefSLRi
4 NEq/2j2VKbHP1enhKcmNHkRBhuiMWnnjts/5FOJqjvQiuFczcX2UKgItJCRBKdoE
5 PAMdIAl3nuIGwRe878ouuOu0N4SU3+Nd3C9IzCDx2zYsl3S5c47ZpGAy/Zo512y9
6 Vt9/bcAYD87P2Yw6keCASuBc6mpKM1r1m6dWk2JEakgYjUR7YkJXglxuyVOhQ3hf
7 8M111pFhAgMBAAECggEBAJxG78wTnLP/9NA4seNC9rRIi17+Sc2YjJuFmC+tAfae
8 P3X9WTseRzjTqaN5L5jkdsY6l1mgCXfSY2+KRwLrB2KAsFVmoAfi9gcuzv/xeEkX
9 gmxJh6k2R2RQzbkkwGL0zmhuwlQdRICJhIZI7k4fiivDpsAJkvluFf/oZgguwXpn
10 F62e7nM2rV4ApH8wN9wixFeAONv9GxiTxjLCYWIMeDP9ETnIsMPTuzpbjHn1cXDt
11 kobmRma93jUzJK2wtsyrvsj7hvYPV+EzHhO8N+VK7FfZ90FBbWQDM+nrxOePVmsY
12 t6KYpVh+B02UtEuVwg+qc7E2bhSxQZzhuuTy27DszWUCgYEA7iIDJtxR8rcNAr5p
13 nMrnJ/ZFtzUMxk1K77hPWN3dLhT5nr70WDUmg2xaHyS8VDk9sIyERCOt+fyngj9Q
14 AWeukD7xwpLzZ0oivK2btT9OG9OCNZYeu4NoWX3ocI2GoHsV3TdLVkSKsWv9Z7EQ
15 EXBkFAGgrclMpWiyGw6sbuKBq68CgYEA68iYUhq13+oTrX2nMBjkah5YsBpInNng
16 B4IOuvcfUf6gsewLpbkcpg4UfUqQxoGsla4mK/5Gd9uIWRENsBZB/ZYKqjQW0bfo
17 kyTXXJy5Phkh/oo4bgSVWGIIiTI3F6tuXu6X75HTiXghm3m/87X3p8AhZk8MURQW
18 dLePVDAih+8CgYEAvylqok2HM3Ki7SryGT4A5mGagYICqUXu/BVXDR29qnqIEFl2
19 SUERk7rtdcbFsE7rKMkEfLavuNiLl9E/ZoFW7tC4vtu8rZQj4pbzQkJ5b3kRM/c4
20 4IqSwBSE/aV/B2EHojf7MFuBgwAPwqevIHC6xhywYhIQh1BOec4Dulf2hF0CgYB9
21 2R+UEzWoQiQmob6u6VphWbk0pZLERXZSC5UZLfXFqgbTcI328orcBv/gr//+NBCO
22 A9nT+XBbYQ2xnGyV5Ats8rzWg976KRM2Fp/siqpE/t0qI1RjRIcCGbE8qVTGiXXr
23 raXi9Q7XfQtTFPTje+in3OD23pJQZExoF+GkqdyEeQKBgEBm6ZzuXYn9hkoBySK8
24 O2sFOUJLE3ptdEdzBHGu1oZNgrTIVIwSykmMwzRtLdJz12gvHs5+hqdvROzZGdHy
25 HAXsEzv8s5RTr1cUGUcCueBiFeiOfvIu6YsFl08WpSIya4bGgOLNRojxvqcfpjn0
26 nyYXiflNy9ffvLvyXKdq0nyW
27 -----END PRIVATE KEY-----
0 -----BEGIN RSA PRIVATE KEY-----
1 MIIEpAIBAAKCAQEAmTDAyUtA9DReWU2We+nK93nNxHA9gtpAZqRBq2cCSWzcrsvP
2 CbwzWw5wdTal4uZdXV5z+8kXmyDQw9/NO6RKvLLyzmNdSHfCJeb3VKBAIDBaq//m
3 8bqpOHqVBFzvtgKDa+l37MahRU3H5fDgatcVKlZ5/VPZxu2DOY5jJst0qwQyosVa
4 bvDqcptds0YlhvMqynr2CxclsPaPW92Y+b6zTWDleahYbWRonDhOoqZxnMnQT0Ie
5 2nM6fD1sgeiSEDhOgEQaC9h7KI3FoPAw3QRWd4b77V0Z/c+NRBPpznVwE3UwgCwe
6 KcRyk4Gq5zR+4/3btzukAvzQbe8OLHS/enW/4wIDAQABAoIBACeCmoaQYT1a8Gaq
7 C0EEaLPxd2/N3x+LuQaAIOvbUoyrhjOTH2AMaVZ33+trX9eowLXfMZzkHbGGAjIy
8 29UhJ6GJqfQvTpTtRmbOLkZmWoOy1P/9rYv1L8YAX8TTT4QrG8hOW/72sAuW5xLY
9 UJldxfi4exgqc0XKZokGv233Fa0xrMYsjZRcqfbafFy2rD1QoN3ceBAUJF+aeB53
10 +DH4X/4URi1kpYHkEM6MOGPfcHq7BFDQDTe/yRZdqPgKvPmWhbDtb4nkDWzeJ1Iy
11 F0xNNy0k2iDNI5ieMQ+peqh5Wrw8awWAs+V8pUp9UGNjJptUEOYE4pj2SYkb5CUs
12 tk9c2JECgYEA8dxwpLMtlrtdzKHphNTqxPFhFfDOljdnYAHjqbnqA86mpanS3oKu
13 ve+3oOuFv3CbrkoTvwo8Chj4sDZAPsuYCGksKL8A0hZ6wdk2WGNsHkRMILG9b+X7
14 a+gLjmiVFO6aak8ahoRbU2eBfhAKh5mTuurJJbyQiN16wI2dwp2M+5sCgYEAoiVR
15 H8v+uMHKN530XlwGZVsiDhNIghceHWPhRJ+8qm387gw4e3A4rXdnLMhR9UdQHjyL
16 7Qu0vttUIhdZrP+pidfcN8QaLG0t5QuSBXNNMqqqMzeD6Y4pX2GoktxL/XYyhZVR
17 ufZebr4wQ12w4T9B6gB5c2NSY4gSkS6xAuO8xVkCgYEAkvfxOyPmQAH7La31yNHZ
18 F3PWGw8Jeh6QoraDMU/X9BhPC7v2d1/R73kLjK2RyJMVBwPcm+oMMdaax/kvcPkm
19 mXXPb7MhPIiMb98eNveza3D1EajwBF8sOJX478B9VwrmqlMHO1aSaEwtU/1LaLra
20 Gmsxb8z1xzVOslNb2jcCxKECgYAKc11HL167icH+069sZYIEBlurjJKfz92hB079
21 nU4LrgsfTKpXSmRcydVcjYy6wl3nlP9vx9Fee8RwbhDZlaDXwZHwBjOpqV/L43MF
22 5uiS220c3/cglokUHLdAv/Il4/hdo8IgukBb4uY5cVB1NB6Ldnxdc4lb4OlRcjD2
23 frcsAQKBgQCq4AlpYVaKf3H8lHwFMokxcDAtWW79Ce99oQ0jakmjduYEbYi4nBbR
24 qo1E7bngYYdVd7nbLBK7mWWnJ4ZgmPnZRTcl0JAZGu0ET5u6KHUrqX9H3J0kwwAN
25 BfII5lKr0gXeR+54+ZcnHTM1yJf/eC/0OKr32LX72LVgj4Sc3fdf5Q==
26 -----END RSA PRIVATE KEY-----
0 -----BEGIN PUBLIC KEY-----
1 MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5vYnoJ85k6qcUFzWFOr9
2 MN2ZWFlgA6nB0Adfm8Ygovg963NrauwTcoPqbfGhi53A8RWc5GT5x1OSjxQW/PAG
3 GfGg3aHZuQMClYWsauodCYG6YgG49WGZODCHZ+TbHMN1pNV+6S+tnZbUtfVKsN34
4 7XyHCyrymmb/OQNAxPyO/76dDR4dB7kWKP4KMMOgDAnA+WDpD1d9/UfBPVZtw/sT
5 yjeJGEOZqSXQW93PKumJ9/DS4azsUMR1JwJA67yWffoHb6sL7QSAQEfp17gDs9as
6 IzITZPPyHNslmY55zaSWEslzFGqPvB7ugUbqH0rjp2TjQ/9Nw7ZKBxukFB9cBUr7
7 v21D2Of2SHQorzRe9lXOwO3BYEt/viypktDnH42qAeoLHDK1ay9ugByTm4ARj14C
8 jslpkEKflk9t9XwUR3kuUaj3w+Y5ItZXFBxns1OQpDt3bMhJFemj7MxkZXt8tvWl
9 UMKVmCUqbnG1eDdTF4VdOovdacMDpvKg438I8MCjaHQf90qIp8MaOjdfSoKBCQi6
10 AP7cpjB+x9xhEuetPYcb/bNjV1OFo4h77XJ+lIH58HBpEG0zkqhtS7JqICIzp3GK
11 7ZG3bhfKKJz0Qr4ISxI0YsYWdmsG38SyMwcomMy4WoLiuQF2QusBSHwBS+p5qQyN
12 kJmXEy8KSQmrrIB/bkiSmgkCAwEAAQ==
13 -----END PUBLIC KEY-----
0 -----BEGIN PUBLIC KEY-----
1 MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuVnS2To8I1Vlx3BLwFgP
2 WDxkUsmquuyp0dpossFkNdJb2WGu6YNSLnlynGTArCDgT4FlNeWIYVAvOdL/SvjI
3 6nTR2Ixh+KTp5yXSmu638fdFRybYRcQ2q6epl4TGVICsXLpzqmnOW3ykVvT1vCn+
4 dFJnkOQaqyxVaWyYAY+7Sos2tkkuRK0V7sdjnphSWiUabiAlE2NGjh8XFryjidyW
5 TqRJL+7oY/SiZe/uoiEhaw/WmUx44SeECsih41fWX2nJvWxxbRri33H8gG5rIXej
6 j/noo6/KxWZV9paVKQcphZama6HRJ4giFvA7L9mLPB4LULsaXKBO6M5yIsAaRJT8
7 /3vmoFtAZ+27Rpy1uYM6gtNg48RcdPLVJRVxKvAvOJ+DTl3ASqUsCnA+N08dK5zd
8 GTpmzGdAzpGknvwMVQstJjPKF2SCDz2q6BpCUV6qoqrJqZXImumC1/grRmLaGVRj
9 b2osO25OWb4+xp/mEL2KFzhkXGWa84JOD4ndVjsaQVIbReNUwNZ1+S7wqaC166Cm
10 OvDn87HbQf3DLFDbU6jfnA47HrSyXF+D5hV5in2BsBU3hzOg3IayN/0lf3aOJ9i3
11 JnLNB9G9ZgPR4FW+s+mbkkDRAo1n/3gZb+awBMsUWYYxal+8doeoeTKWZXUSKBmW
12 nmcMVCMgP9It2wUndVPrFb8CAwEAAQ==
13 -----END PUBLIC KEY-----
0 -----BEGIN PUBLIC KEY-----
1 MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx+OAdmqYfr5U1VA77adP
2 MrcjC7Lr6DkwTIpP7iO9aTL+FwFAa+b1n+NpBPC7WjFak6oT+P69kcEBIiofERL6
3 Ijje2TvO8UKtZwqmwPmhmrFFptoVxDn5YSJmUevsaWxhY+2eL4tQBR43gEKd5WAE
4 URQdUWt1+itY0tjzZpIcbHYkSXnoVsO8IdI0d4sfX5Kg/6oWxusJw9FyBoxYr8Z/
5 YSP3oxri8i/RTcOseeQh5U2Rp4Pe4E6iGto10I5JCIoxQqdeMmaZgAoxA3EbqHbo
6 7XAB9enPlx/epXvRg8NP0VP1ZBPRScNDqgQOszqSy9lVYEAuLx6hvkS5czMIuHI3
7 aCBFTBeyhWa8ffjPE9xssdKOjyS1y2ET6xXUqnjI+92bZe+vg3+6zuM+wlas+QFC
8 i5h4aNhqF/WhBvjs2wuXlsaNpcUrHMUpo+vmKuollfA3rLtWx6Gw9V4bxR4mcSxk
9 weVCEuki+5ai+F3QsDP4c7Nmk9O+YP9Ugb0Vhj17AgFJbiQ6kvE3ABQaDhe+2jVc
10 w/Wen8VRUkNQS8Mom0NkpqfTW5tb0bcZJHYIPBma85t6/81CwysoaUAYvw/593wx
11 c4wCCa2Rw+UrkKY0h3T37os7PrzQ7l2iPJfRFjTWOxOp3zabVEIDMi8A76HFwT/E
12 6I0FvsVPLTIf24c6cmqPndECAwEAAQ==
13 -----END PUBLIC KEY-----
0 #
1 # This is the "override security properties file" which is used by default
2 # in the lein dev profile. End users may override java security properties in
3 # a similar manner in the production code.
4 #
5 # This file augments and overrides $JAVA_HOME/jre/lib/security/java.security
6 # when the java process is provided the option,
7 # -Djava.security.properties=./dev-resources/java.security
8 #
9 # NOTE: It is possible to make this file authoritative, discarding the values
10 # in $JAVA_HOME/jre/lib/security/java.security by setting the first character
11 # of the path to an '=' sign.
12 #
13 # Algorithm restrictions for Secure Socket Layer/Transport Layer Security
14 # (SSL/TLS) processing
15
16 # In some environments, certain algorithms or key lengths may be undesirable
17 # when using SSL/TLS. This section describes the mechanism for disabling
18 # algorithms during SSL/TLS security parameters negotiation, including
19 # protocol version negotiation, cipher suites selection, peer authentication
20 # and key exchange mechanisms.
21 #
22 # Disabled algorithms will not be negotiated for SSL/TLS connections, even
23 # if they are enabled explicitly in an application.
24 #
25 # For PKI-based peer authentication and key exchange mechanisms, this list
26 # of disabled algorithms will also be checked during certification path
27 # building and validation, including algorithms used in certificates, as
28 # well as revocation information such as CRLs and signed OCSP Responses.
29 # This is in addition to the jdk.certpath.disabledAlgorithms property above.
30 #
31 # See the specification of "jdk.certpath.disabledAlgorithms" for the
32 # syntax of the disabled algorithm string.
33 #
34 # Note: This property is currently used by Oracle's JSSE implementation.
35 # It is not guaranteed to be examined and used by other implementations.
36 #
37 # Example:
38 # jdk.tls.disabledAlgorithms=MD5, SSLv3, DSA, RSA keySize < 2048
39 #
40 # Disable no algorithms so that unit tests are able to exercise the behavior of
41 # the system when the end user explicitly configures deprecated algorithms like
42 # SSLv3.
43 jdk.tls.disabledAlgorithms=
0 <configuration scan="true">
1 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
2 <encoder>
3 <pattern>%d %-5p [%c{2}] %m%n</pattern>
4 </encoder>
5 </appender>
6
7 <logger name="org.apache.http" level="warn"/>
8 <logger name="org.eclipse.jetty" level="warn"/>
9
10 <root level="warn">
11 <appender-ref ref="STDOUT" />
12 </root>
13 </configuration>
0 <configuration debug="false">
1
2 <appender name="LIST" class="appender.TestListAppender">
3 <encoder>
4 <pattern>%h %l %u %user %date "%r" %s %b</pattern>
5 </encoder>
6 </appender>
7
8 <appender-ref ref="LIST" />
9 </configuration>
0 ## Configuring The Webserver Service
1
2 The `webserver` section in your Trapperkeeper configuration files configures an
3 embedded HTTP server inside trapperkeeper.
4
5 ### `host`
6
7 This sets the hostname to listen on for _unencrypted_ HTTP traffic. If not
8 supplied, we bind to `localhost`, which will reject connections from anywhere
9 but the server process itself. To listen on all available interfaces,
10 use `0.0.0.0`.
11
12 ### `port`
13
14 This sets what port to use for _unencrypted_ HTTP traffic. If not supplied, but
15 `host` is supplied, a value of 8080 will be used. If neither host nor port is
16 supplied, we won't listen for unencrypted traffic at all.
17
18 ### `acceptor-threads`
19
20 This sets the number of threads that the webserver will dedicate to accepting
21 socket connections for _unencrypted_ HTTP traffic. Defaults to the number of
22 virtual cores on the host divided by 8, with a minimum of 1 and maximum of 4.
23
24 ### `selector-threads`
25
26 This sets the number of threads that the webserver will dedicate to processing
27 events on connected sockets for _unencrypted_ HTTP traffic. Defaults to the
28 number of virtual cores on the host divided by 2, with a minimum of 1 and
29 maximum of 4.
30
31 ### `max-threads`
32
33 This sets the maximum number of threads assigned to responding to HTTP and/or
34 HTTPS requests for a single webserver, effectively changing how many concurrent
35 requests can be made at one time. Defaults to 200.
36
37 Each webserver instance requires a minimum number of threads in order to boot
38 properly. The minimum number is calculated as:
39
40 ~~~~
41 (number of "acceptor-threads" for each port) +
42 (number of "selector-threads" for each port) +
43 1 "worker" thread
44 ~~~~
45
46 For example, if an _unencrypted_ port with 2 `acceptor-threads` and 3
47 `selector-threads` and an _encrypted_ port with 4 `acceptor-threads` and 5
48 `selector-threads` were configured with the webserver, the webserver would
49 require that a minimum value of 15 (2 + 3 + 4 + 5 + 1) be used for the
50 `max-threads` setting. If the configured value for `max-threads` is less
51 than the minimum required value, server startup will fail with an
52 `IllegalStateException`, with a message containing the words
53 "insufficient threads".
54
55 Note that each web request must be processed on a "worker" thread which is
56 separate from the acceptor and selector threads. "1" is the minimum number of
57 worker threads required to process incoming web requests. The `max-threads`
58 value should be large enough that the server can allocate all of the selector
59 and acceptor threads that it needs and yet still have a sufficient number of
60 worker threads left over for handling concurrent web requests.
61
62 ### `queue-max-size`
63
64 This can be used to set an upper-bound on the size of the worker queue that the
65 web server uses to temporarily store incoming client connections before they
66 can be serviced. This value defaults to the maximum value of a 32-bit signed
67 integer, 2147483647. A request which is rejected by the web server because the
68 queue is full would be seen by the client as having initially connected to the
69 server socket at the TCP layer but having been closed almost immediately
70 afterward by the server with no HTTP layer response body.
71
72 ### `request-body-max-size`
73
74 This sets the maximum size, in bytes, of the body for an HTTP request. The size
75 of the request body is determined from the value for the request's HTTP
76 Content-Length header. If the Content-Length exceeds the configured value, Jetty
77 will return an HTTP 413 Error response. If this setting is not configured and/or
78 the request does not provide a Content-Length header, Jetty will pass the
79 request through to underlying handlers (bypassing Content-Length evaluation).
80
81 ### `request-header-max-size`
82
83 This sets the maximum size of an HTTP Request Header. If a header is sent
84 that exceeds this value, Jetty will return an HTTP 413 Error response. This
85 defaults to 8192 bytes, and only needs to be configured if an exceedingly large
86 header is being sent in an HTTP Request.
87
88 ### `so-linger-seconds`
89
90 This sets the TCP SO_LINGER time, in seconds, that the webserver uses for
91 underlying socket connections. Values less than 0 result in SO_LINGER
92 being disabled. Defaults to -1, i.e., "disabled". For a more detailed
93 description of what it means to have SO_LINGER disabled vs. enabled for some
94 number of seconds, see http://man7.org/linux/man-pages/man7/socket.7.html. Note
95 that the effect of setting this option may vary depending upon the operating
96 system's underlying implementation.
97
98 ### `idle-timeout-milliseconds`
99
100 This optional setting can be used to control how long Jetty will allow a
101 connection to be held open by a client without any activity on the socket. If
102 a connection is idle for longer than this value, Jetty will forcefully close
103 it from the server side. Jetty's default value for this setting is 30 seconds.
104 Note that Jetty will not automatically close the connection if the idle timeout
105 is reached while Jetty is still actively processing a client request.
106
107 ### `ssl-host`
108
109 This sets the hostname to listen on for _encrypted_ HTTPS traffic. If not
110 supplied, we bind to `localhost`. To listen on all available interfaces,
111 use `0.0.0.0`.
112
113 ### `ssl-port`
114
115 This sets the port to use for _encrypted_ HTTPS traffic. If not supplied, but
116 `ssl-host` is supplied, a value of 8081 will be used for the https port. If
117 neither ssl-host nor ssl-port is supplied, we won't listen for encrypted traffic
118 at all.
119
120 ### `ssl-acceptor-threads`
121
122 This sets the number of threads that the webserver will dedicate to accepting
123 socket connections for _encrypted_ HTTPS traffic. Defaults to the number of
124 virtual cores on the host divided by 8, with a minimum of 1 and maximum of 4.
125
126 ### `ssl-selector-threads`
127
128 This sets the number of threads that the webserver will dedicate to processing
129 events on connected sockets for _encrypted_ HTTPS traffic. Defaults to the
130 number of virtual cores on the host divided by 2, with a minimum of 1 and
131 maximum of 4.
132
133 ### `ssl-cert`
134
135 This sets the path to the server certificate PEM file used by the web
136 service for HTTPS. During the SSL handshake for a connection, certificates
137 extracted from this file are presented to the client for the client's use in
138 validating the server. This file may contain a single certificate or a chain
139 of certificates ordered from the end certificate first to the most-root
140 certificate last. For example, a certificate chain could contain:
141
142 * An end certificate
143 * An intermediate CA certificate with which the end certificate was issued
144 * A root CA certificate with which the intermediate CA certificate was issued
145
146 In the PEM file, the end certificate should appear first, the intermediate CA
147 certificate should appear second, and the root CA certificate should appear
148 last.
149
150 If a chain is present, it is not required to be complete. If a
151 path has been specified for the `ssl-cert-chain` setting, the server will
152 construct the cert chain starting with the first certificate found in the
153 `ssl-cert` PEM and followed by any certificates in the `ssl-cert-chain` PEM. In
154 the latter case, any certificates in the `ssl-cert` PEM beyond the first one
155 would be ignored.
156
157 > **Note:** This setting overrides the alternate configuration settings
158 `keystore` and `key-password`.
159
160 ### `ssl-cert-chain`
161
162 This sets the path to a PEM with CA certificates for use in presenting a
163 client with the server's chain of trust. Certs found in this PEM file are
164 appended after the first certificate from the `ssl-cert` PEM in the
165 construction of the certificate chain. This is an optional setting. The
166 certificates in the `ssl-cert-chain` PEM file should be ordered from the
167 least-root CA certificate first to the most-root CA certificate last. For
168 example, a certificate chain could contain:
169
170 * An end certificate
171 * An intermediate CA certificate with which the end certificate was issued
172 * A root CA certificate with which the intermediate CA certificate was issued
173
174 The end certificate should appear in the `ssl-cert` PEM file. In the
175 `ssl-cert-chain` PEM file, the intermediate CA certificate should appear
176 first and the root CA certificate should appear last.
177
178 The chain is not required to be complete.
179
180 > **Note:** This setting overrides the alternate configuration settings
181 `keystore` and `key-password`.
182
183 ### `ssl-key`
184
185 This sets the path to the private key PEM file that corresponds with the
186 `ssl-cert`, it used by the web service for HTTPS.
187
188 > **Note:** This setting overrides the alternate configuration settings
189 `keystore` and `key-password`.
190
191 ### `ssl-ca-cert`
192
193 This sets the path to the CA certificate PEM file used for client
194 authentication. The PEM file may contain one or more CA certificates.
195 Authorized clients must have been signed - either directly or via an
196 intermediate CA - using one of the CA certificates in the PEM file.
197
198 > **Note:** This setting overrides the alternate configuration settings
199 `truststore` and `trust-password`.
200
201 ### `keystore`
202
203 This sets the path to a Java keystore file containing the key and certificate
204 to be used for HTTPS.
205
206 ### `key-password`
207
208 This sets the passphrase to use for unlocking the keystore file.
209
210 ### `truststore`
211
212 This describes the path to a Java keystore file containing the CA certificate(s)
213 for your infrastructure.
214
215 ### `trust-password`
216
217 This sets the passphrase to use for unlocking the truststore file.
218
219 ### `cipher-suites`
220
221 Optional. The cryptographic ciphers to allow for incoming SSL
222 connections. This may be formatted either as a list (in a HOCON
223 configuration file) or a comma-separated string. Valid names are
224 listed in the
225 [official JDK cryptographic providers documentation](http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SupportedCipherSuites);
226 you'll need to use the all-caps cipher suite name.
227
228 If not supplied, trapperkeeper uses this list of cipher suites:
229
230 - `TLS_RSA_WITH_AES_256_CBC_SHA256`
231 - `TLS_RSA_WITH_AES_256_CBC_SHA`
232 - `TLS_RSA_WITH_AES_128_CBC_SHA256`
233 - `TLS_RSA_WITH_AES_128_CBC_SHA`
234 - `SSL_RSA_WITH_RC4_128_SHA`
235 - `SSL_RSA_WITH_3DES_EDE_CBC_SHA`
236 - `SSL_RSA_WITH_RC4_128_MD5`
237
238 ### `ssl-protocols`
239
240 Optional. The protocols to allow for incoming SSL connections. This
241 may be formatted either as a list (in a HOCON configuration file) or a
242 comma-separated string. Valid names are listed in the
243 [official JDK cryptographic protocol documentation](http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SunJSSEProvider);
244 you'll need to use the names with verbatim capitalization. For
245 example: `TLSv1, TLSv1.1, TLSv1.2`.
246
247 If not supplied, trapperkeeper uses this list of SSL protocols:
248
249 - `TLSv1`
250 - `TLSv1.1`
251 - `TLSv1.2`
252
253
254 ### `client-auth`
255
256 Optional. This determines the mode that the server uses to validate the
257 client's certificate for incoming SSL connections. One of the following
258 values may be specified:
259
260 * `need` - The server will request the client's certificate and the certificate
261 must be provided and be valid. The certificate must have been issued
262 by a Certificate Authority whose certificate resides in the
263 `truststore`.
264
265 * `want` - The server will request the client's certificate. A certificate, if
266 provided by the client, must have been issued by a Certificate
267 Authority whose certificate resides in the `truststore`. If the
268 client does not provide a certificate, the server will still consider
269 the client valid.
270
271 * `none` - The server will not request a certificate from the client and will
272 consider the client valid.
273
274 If a value is not provided for this setting, `need` will be used as the default
275 value.
276
277 ### `ssl-crl-path`
278
279 Optional. This describes a path to a Certificate Revocation List file. Incoming
280 SSL connections will be rejected if the client certificate matches a
281 revocation entry in the file.
282
283 ### `static-content`
284
285 Optional. This is a list of static content to be added to the server as context handlers
286 during initialization. Each item in this list should be a map containing two keys. The first,
287 `resource`, is the path to the resource you want added as a context handler (the equivalent of
288 the `base-path` argument of the `add-context-handler` service function). The second, `path`,
289 is the URL endpoint at which you want to mount the context handler (the equivalent of the
290 `context-path` argument of the `add-context-handler` service function).
291
292 For example, say you have a `web-assets` directory containing a file called `image.jpg`.
293 If your configuration were like so:
294
295 ```
296 webserver: {
297 port: 8080
298 static-content: [{resource: "./web-assets"
299 path: "/assets"}]
300 }
301 ```
302
303 Then the static content in the `web-assets` directory would be mounted at the URL endpoint
304 `"/assets"` on your server during initialization, and you could access the contents of
305 `image.jpg` by visiting `"http://localhost:8080/assets/image.jpg"`.
306
307 By default, symbolic links will not be served by the Jetty9 Webservice. However, if you have
308 a symbolic link that you want to serve as static content, you can add an extra option,
309 `follow-links`, to the specification for a piece of static content. The value of this should
310 be a boolean, and if set to true, symbolic links will be served.
311
312 For example, say that you have a symbolic link in your `web-assets` directory, `image-link`,
313 that links to the `image.jpg` file. If you want this to be served, you would configure
314 your static content like so:
315
316 ```
317 webserver: {
318 port: 8080
319 static-content: [{resource: "./web-assets"
320 path: "/assets"
321 follow-links: true}]
322 }
323 ```
324 Since `follow-links` is set to true, `image-link` will now be served, and can
325 be accessed by visiting `"http://localhost:8080/assets/image-link"`.
326
327 ### `gzip-enable`
328
329 Optional. This controls whether or not the webserver could compress the
330 response body for any request using Gzip encoding. A value of `false` would
331 prevent the server from using Gzip encoding the response body for all requests.
332 If this option is not specified or is specified with a value of `true`, the
333 webserver "could" Gzip encode the response body.
334
335 Note that in order for Gzip encoding to be used, a client would also need to
336 include in the request an "Accept-Encoding" HTTP header containing the value
337 "gzip". The webserver also may use other heuristics to avoid Gzip encoding the
338 response body independent of the configuration of this setting. For example,
339 the webserver may skip compression for a sufficiently small response body.
340
341 ### `access-log-config`
342
343 Optional. This is a path to an XML file containing configuration information
344 for the `Logback-access` module. If present, a logger will be set up to log
345 information about any HTTP requests Jetty receives according to the logging configuration,
346 as long as the XML file pointed to exists and is valid. Information on configuring the
347 `Logback-access` module is available [here](http://logback.qos.ch/access.html#configuration).
348
349
350 An example configuration file can be found [here](request-logging-example-config.xml). This
351 example configures a `FileAppender` that outputs to a file, `access.log`, in the `dev-resources`
352 directory. It will log the remote host making the request, the log name, the remote user making
353 the request, the date/time of the request, the URL and method of the request, the status of
354 the response, and the size in bytes of the response.
355
356 ### `shutdown-timeout-seconds`
357
358 Optional. This is an integer representing the desired graceful stop timeout in seconds.
359 Defaults to 30 seconds.
360
361 ### `post-config-script`
362
363 Optional. This setting is for advanced use cases only, and is intended for
364 debugging purposes. You can use it to modify low-level Jetty settings that
365 are not directly exposed in our normal configuration options. In most cases,
366 if you find yourself using this, it is an indicator that we need to expose
367 additional settings directly in our main configuration (so please let us know!).
368 Also, the implementation details of this setting may change between releases.
369
370 If you do need to use this, you can set the value to a String containing some
371 Java code that should be executed against the Jetty `Server` object. This object
372 will be injected into the scope of your code in a variable named `server`.
373
374 Here is a pathological example that shows how you could change the port that
375 your server listens on (which you could achieve in a much simpler fashion by
376 using the existing `port` setting; this example is only for the purposes of
377 illustration):
378
379 post-config-script: "import org.eclipse.jetty.server.ServerConnector;
380 ServerConnector c = (ServerConnector)(server.getConnectors()[0]);
381 c.setPort(10000);"
382
383 For more info on the Jetty `Server` object model, see the
384 [Jetty Javadocs](http://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/server/Server.html).
385
386 ## Configuring multiple webservers on isolated ports
387
388 It is possible to configure multiple webservers on isolated ports within a single Jetty9
389 webservice. In order to configure multiple webservers, change the `webserver` section of your
390 Trapperkeeper configuration files to be a nested map. Each key in this map is the id of a server, and
391 its value is the configuration for that server.
392
393 For example, say you wanted to configure two servers on localhost, one on port 9000 and one on port
394 10000. The webserver section of your configuration file would look something like this:
395
396 ```
397 webserver: {
398 bar: {
399 host: localhost
400 port: 9000
401 }
402
403 foo: {
404 host: localhost
405 port: 10000
406 }
407 }
408 ```
409
410 This configuration would cause the Jetty9 service to create two different Jetty servers on isolated
411 ports. You can then specify which server you would like to add handlers to when calling the Jetty9
412 service functions, and they will be added to the server you specify.
413
414 Please note that, with the above configuration, you MUST specify a server-id when calling a service
415 function, or else the operation will fail. If you would like to have a multi-server configuration
416 and NOT specify a server-id when calling some service functions, you can optionally specify a
417 default server in your configuration file. Then, if no server-id is specified when performing
418 an operation, the operation will automatically be performed on the default server.
419
420 To specify a default server, add a `:default-server` key with a value of `true` to the configuration
421 information for one of your servers in your trapperkeeper configuration. For example:
422
423 ```
424 webserver: {
425 bar: {
426 host: localhost
427 port: 9000
428 default-server: true
429 }
430
431 foo: {
432 host: localhost
433 port: 10000
434 }
435 }
436 ```
437
438 The above configuration would set up two servers as in the previous example, except
439 the server with id `:bar` would be set as the default server. Calling a service function
440 without specifying a server-id will cause the operation to be performed on the server with
441 id `:bar`.
442
443 Please note that only one server can be specified as the default server. Please also note that
444 setting a default server is optional. It is only required if you are planning to call a service
445 function without passing in a server-id in a multi-server set-up.
446
447 Note that you are NOT limited to two servers and can configure more according to your needs.
448
449 Also note that you can still set the `webserver` section of your configuration to be an un-nested map
450 containing a single webserver configuration, like so
451
452 ```
453 webserver: {
454 host: localhost
455 port: 9000
456 }
457 ```
458
459 In this case, the Jetty9 Service will simply create a single webserver and give it id `:default`,
460 and will automatically make this server the default server.
461
462 ### `jmx-enable`
463
464 Optional. When enabled this setting will register the Jetty 9 MBeans so they are visible via
465 JMX. Useful for monitoring the state of your Jetty 9 instance while it is running; for monitoring and
466 debugging purposes. Defaults to `true`.
0 <configuration debug="false">
1
2 <appender name="FILE" class="ch.qos.logback.core.FileAppender">
3 <file>./dev-resources/access.log</file>
4 <encoder>
5 <pattern>%h %l %u %user %date "%r" %s %b</pattern>
6 </encoder>
7 </appender>
8
9 <appender-ref ref="FILE" />
10 </configuration>
0 # TrapperKeeper Webserver Service Test Utils
1
2 The trapperkeeper webserver service library provides some
3 [utility code](../test/clj/puppetlabs/trapperkeeper/testutils)
4 for use in tests. The code is available in a separate "test" jar that you may depend
5 on by using a classifier in your project dependencies.
6
7 ```clojure
8 (defproject yourproject "1.0.0"
9 ...
10 :profiles {:dev {:dependencies [[puppetlabs/trapperkeeper-webserver-jetty9 "x.y.z" :classifier "test"]]}})
11 ```
12
13 The test jar contains a macro to assist in testing the functionality of a ring application.
14 You can find the macro in [webserver.clj](../test/puppetlabs/trapperkeeper/testutils/webserver.clj).
15
16 ### with-test-webserver
17
18 The `with-test-webserver` macro starts up a new web server which is bound to a random unused port, and attaches a
19 provided Ring handler function. When the test is completed `with-test-webserver` also handles shutting down the web server.
20
21 The first parameter provided to the `with-test-webserver` macro is a ring handler function (see
22 [ring concepts](https://github.com/ring-clojure/ring/wiki/Concepts)) which will generally by a handler that exists in
23 your _trapperkeeper_ application somewhere. The second parameter is an identifier which will contain the port number
24 that the web server was started on.
25
26 Generally, inside the body of the `with-test-webserver` macro a number of web requests are made and their responses are
27 examined for correctness. For example:
28
29 ```clojure
30 (with-test-webserver app port
31 (testing "a gzipped response when requests"
32 ;; The client/get function asks for compression by default
33 (let [resp (http-client/get (format "http://localhost:%d/" port))]
34 (is (= (resp :body) body))
35 (is (= (get-in resp [:headers "content-encoding"]) "gzip")
36 (format "Expected gzipped response, got this response: %s" resp))))
37 ```
0 ## Configuring The Webrouting Service
1
2 The `web-router-service` section in your Trapperkeeper configuration files
3 configures the endpoints for your webrouter service.
4
5 Each key in the `web-router-service` section is the namespaced symbol of
6 a service. The value stored in this key can be one of two things.
7
8 If only specifying one endpoint for a particular service, this value can
9 be a string containing a URL endpoint. This will be the only endpoint
10 available for the service it is configured for, and it will automatically
11 be assigned route-id `:default`. This can be done like so:
12
13 ```
14 web-router-service: {
15 "puppetlabs.foo/foo-service": "/foo"
16 }
17 ```
18
19 It is also possible to configure multiple web endpoints. In this case,
20 the value will be a map instead of a string. Each key in this map
21 will contain a URL endpoint stored in a string. They key is the route-id
22 for that endpoint. This can be done like so:
23
24 ```
25 web-router-service: {
26 "puppetlabs.foo/foo-service": {
27 foo: "/foo"
28 bar: "/bar"
29 }
30 }
31 ```
32
33 In this case, two endpoints will be configured for the `foo-service`.
34 `"/foo"` will have route-id `:foo`, and `"/bar"` will have route-id
35 `:bar`. Handlers can be added to the `"/bar"` endpoint by explicitly
36 specifying `:bar` as the `route-id` when adding a handler. Please see
37 [Trapperkeeper Webrouting Service](webrouting-service.md) for
38 more information.
39
40 In the case where you have configured multiple servers, you can configure
41 the webrouting service to add specific endpoints to specific servers. For
42 example, say you have two servers, one with id `:foo` and one with id `:bar`.
43 Say you want the endpoint for a service to be added to the server with id
44 `:foo`. You could do this like so:
45
46 ```
47 web-router-service: {
48 "puppetlabs.foo/foo-service": {
49 route: "/foo"
50 server: "foo"
51 }
52 }
53 ```
54
55 You can do the same thing when you have multiple routes configured for a
56 service:
57
58 ```
59 web-router-service: {
60 "puppetlabs.foo/foo-service": {
61 foo: {
62 route: "/foo"
63 server: "foo"
64 }
65 bar: {
66 route: "/bar"
67 server: "bar"
68 }
69 }
70 }
71 ```
72
73 In this case, adding a handler to endpoint `:foo` would add it to the
74 server with id `:foo` at path "/foo". Adding a handler to endpoint
75 `bar` would add it to the server with id `:bar` at path "/bar".
76
77 Note that, if no server is specified for an endpoint and there are
78 multiple servers, the endpoint will be added to the default server.
79 If no default server is set, a server MUST be provided for every
80 endpoint.
81
82 Also note that, because the webrouting service is built on top of the
83 webserver service, the webserver service will need to be included in your
84 `bootstrap.cfg` file, and the webserver service will need to be configured in
85 your trapperkeeper configuration files. Please see
86 [Configuring the Webserver](jetty-config.md) for more details.
87
0 ## Trapperkeeper Webrouting Service
1
2 This project additionally provides a webrouting service, which acts as a
3 wrapper around the Trapperkeeper Webserver Service, also contained in this
4 project. This service is for use with the
5 [trapperkeeper service framework.](https://github.com/puppetlabs/trapperkeeper)
6
7 The Webrouting Service is an optional service that allows you to manage the
8 configuration of your web service URLs in a different manner. It is a thin
9 wrapper around the Webserver Service, and it allows you to consolidate all
10 of your URL endpoints in a single section of your trapperkeeper configuration.
11
12 When using the Webserver Service to directly register web endpoints, the endpoints
13 get scattered throughout the code base, and it can be difficult to determine what
14 endpoints are running in your server and which services registered them. With the
15 webrouting service, all this information is stored in your configuration file. It
16 is easy to determine which endpoints are running on your server and which services
17 registered those endpoints.
18
19 For example:
20
21 ```
22 web-router-service: {
23 "puppetlabs.foo/foo-service": "/foo"
24 "puppetlabs.bar/bar-service": {
25 bar: "/bar"
26 baz: "/baz"
27 }
28 }
29 ```
30
31 The services specified in the above configuration would use the Webrouting Service
32 instead of the Webserver Service to register web endpoints. A
33 developer/user/administrator can simply look at the trapperkeeper configuration and
34 determine there are web endpoints registered at '/foo', '/bar/, and '/baz', and that
35 these are registered in the clojure namespaces 'puppetlabs.foo' and 'puppetlabs.bar'.
36
37 To use this service in your trapperkeeper application, simply add this project
38 as a dependency in your leiningen project file, and then add the webrouting
39 service to your [`bootstrap.cfg`](https://github.com/puppetlabs/trapperkeeper#bootstrapping)
40 file, via:
41
42 puppetlabs.trapperkeeper.services.webrouting.webrouting-service/webrouting-service
43
44 The webrouting service is configured via the
45 [trapperkeeper configuration service](https://github.com/puppetlabs/trapperkeeper#configuration-service).
46 Please see [Configuring the Webrouting Service](webrouting-config.md) for information on
47 how to configure the webrouting service.
48
49 ### Service Protocol
50
51 This is the protocol for the current implementation of the `:WebroutingService`:
52
53 ```clj
54 (defprotocol WebroutingService
55 (get-route [this svc] [this svc route-id])
56 (get-server [this svc] [this svc route-id])
57 (add-context-handler [this svc context-path] [this svc context-path options])
58 (add-ring-handler [this svc handler] [this svc handler options])
59 (add-servlet-handler [this svc servlet] [this svc servlet options])
60 (add-war-handler [this svc war] [this svc war options])
61 (add-websocket-handler [this svc handlers] [this svc handlers options])
62 (add-proxy-route [this svc target] [this svc target options])
63 (override-webserver-settings! [this overrides] [this server-id overrides])
64 (get-registered-endpoints [this] [this server-id])
65 (log-registered-endpoints [this] [this server-id])
66 (join [this] [this server-id]))
67 ```
68
69 #### `get-route`
70
71 This function allows you to get the web-route for a particular service
72 as configured in your configuration file. The one-argument version will
73 return the web route configured for the current service in a single-route
74 configuration. The two
75 argument version will return the web route configured for the current
76 service with the id you specify.
77
78 Note that the one argument version cannot be used with a service that
79 has multiple webroutes configured.
80
81 #### `get-server`
82
83 This function allows you to get the server for a particular service
84 as configured in your configuration file. The one-argument version will
85 return the server configured for the current service in a single-route
86 configuration. The two
87 argument version will return the server configured for the web route with
88 the `route-id` that you specify configured for the current service.
89
90 Note that both the one and two argument versions will return nil if
91 the service does not have a server value configured.
92
93 #### Other functions
94
95 The functions `override-webserver-settings!`, `get-registered-endpoints`,
96 `log-registered-endpoints`, and `join` all work in the exact same way as
97 their corresponding functions in the webserver service, and are there so that
98 you don't need to specify a dependency on the Webserver Service.
99
100 The other functions do the same thing as their Webserver Service counterparts. However,
101 instead of taking an explicit path as an argument, these functions take a service,
102 `svc`. `svc` should be the service calling the function. Instead of having an explicit
103 endpoint passed in as an argument, these functions will use the service given to them to
104 find the endpoint configured for that service in the configuration file. So, for example,
105 with the Webserver service, you would call
106
107 ```clj
108 (add-ring-handler my-app "/my-app")
109 ```
110
111 which would add the ring handler `my-app` to the endpoint `"/my-app"`. With the webrouting
112 service, however, you would call
113
114 ```clj
115 (add-ring-handler this my-app)
116 ```
117
118 which would find the endpoint configured for the current service in the configuration file,
119 then register the ring handler `my-app` at that endpoint.
120
121 The options map for each of these functions is identical to those in the corresponding
122 webserver service functions, with two exceptions.
123
124 First, they can take an additional, optional
125 key, `:route-id`. This is used when multiple endpoints are configured for a specific
126 service, with its value being the id of the specific endpoint you want to add the handler to.
127 In a multiroute configuration, a route-id MUST be specified or the operation will fail.
128
129 Second, `:server-id` is a disallowed key in this options map. Specifying a specific server
130 to which to add an endpoint is handled in the configuration of the webrouting service.
131
132 As an example, say you decide to add two endpoints using a specific service, and you have
133 two endpoints configured for that service.
134 One is endpoint `"/foo"` and is kept at key `:foo`. The other is
135 endpoint `"/bar"` and is kept at key `:bar`. If you were to call
136
137 ```clj
138 (add-ring-handler this my-app {:route-id :foo)
139 ```
140
141 the ring handler `my-app` would be registered at endpoint `"/foo"`. However, if you were to call
142
143 ```clj
144 (add-ring-handler this my-app {:route-id :bar})
145 ```
146
147 the ring handler `my-app` would be registered at endpoint `"/bar"`.
148
149 For information on how to configure multiple endpoints, please see
150 [Configuring the Webrouting Service](webrouting-config.md).
0 Sample Trapperkeeper Multiserver Web App
1 -----------------------------------------
2
3 To run the app, use this command:
4
5 ```sh
6 lein trampoline run --config examples/multiserver_app/multiserver-example.conf \
7 --bootstrap-config examples/multiserver_app/bootstrap.cfg
8
9 ```
10
11 Open
12
13 ```
14 http://localhost:8080/hello
15
16 ```
17
18 in your browser to see the famous Hello World message.
19
20 Open
21
22 ```
23 http://localhost:9000/hello
24
25 ```
26
27 in your browser to see the same message. Note that this is on a separate server on a different port.
28
29 Open
30
31 ```
32 http://localhost:9000/goodbye
33
34 ```
35
36 in your browser to see a "Goodbye world" message. Note that this response is NOT displayed at address
37
38 ```
39 http://localhost:8080/goodbye
40
41 ```
42
43 due to the fact that it was added specifically to the server on port 9000 and NOT the server on port
44 8080.
0 puppetlabs.trapperkeeper.services.nrepl.nrepl-service/nrepl-service
1 puppetlabs.trapperkeeper.services.webserver.jetty9-service/jetty9-service
2 examples.multiserver-app.example-services/hello-web-service
3 examples.multiserver-app.example-services/hello-proxy-service
0 <configuration scan="true">
1 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
2 <encoder>
3 <pattern>%d %-5p [%c{2}] %m%n</pattern>
4 </encoder>
5 </appender>
6
7 <root level="info">
8 <appender-ref ref="STDOUT" />
9 </root>
10 </configuration>
0 global: {
1 # Points to a logback config file
2 logging-config: examples/multiserver_app/logback.xml
3 }
4
5 nrepl: {
6 enabled: true
7 }
8
9 webserver: {
10 bar: {
11 port: 8080
12 default-server: true
13 }
14 foo: {
15 port: 9000
16 }
17 }
18
19 hello-web: {
20 url-prefix = /hello
21 }
22
0 (ns examples.multiserver-app.example-services
1 (:require [clojure.tools.logging :as log]
2 [puppetlabs.trapperkeeper.core :refer [defservice]]))
3
4 (defservice hello-web-service
5 [[:ConfigService get-in-config]
6 [:WebserverService add-ring-handler]]
7 (init [this context]
8 (log/info "Initializing hello webservice")
9 (let [url-prefix (get-in-config [:hello-web :url-prefix])]
10 ; Since we're using add-ring-handler, the ring handler will be added to the :default
11 ; server specified in the config file automatically
12 (add-ring-handler
13 (fn [req]
14 {:status 200
15 :headers {"Content-Type" "text/plain"}
16 :body "Hello, World!"})
17 url-prefix)
18 (assoc context :url-prefix url-prefix))))
19
20 (defservice hello-proxy-service
21 [[:ConfigService get-in-config]
22 [:WebserverService add-proxy-route add-ring-handler]]
23 (init [this context]
24 (log/info "Initializing hello webservice")
25 (let [url-prefix (get-in-config [:hello-web :url-prefix])]
26 ; Since we're using the -to versions of the below functions and are specifying
27 ; server-id :foo, these will be added to the :foo server specified in the
28 ; config file.
29 (add-proxy-route
30 {:host "localhost"
31 :port 8080
32 :path "/hello"}
33 "/hello"
34 {:server-id :foo})
35 (add-ring-handler
36 (fn [req]
37 {:status 200
38 :headers {"Content-Type" "text/plain"}
39 :body "Goodbye world"})
40 "/goodbye"
41 {:server-id :foo})
42 (assoc context :url-prefix url-prefix))))
0 # Simple Web Service Example
1
2 This example demonstrates how to create a simple set of web services which both depend upon a hit counter service for
3 generating content. When run, this code will attach two endpoints, `/bert` and `/ernie` which will generate a simple
4 block of HTML that displays seperate hit counters for each service.
5
6 All code needed to execute this example is located in `examples/ring_app`. The Clojure code is
7 contained in the `example_services.clj` file.
8
9 And now, a few quick housekeeping items before we get to the code...
10
11 ## Launching trapperkeeper and running the app
12
13 To start up _trapperkeeper_ and launch the sample application, use the following _lein_ command while in the
14 _trapperkeeper-webserver-jetty9_ home directory:
15
16 ```sh
17 lein trampoline run --config examples/ring_app/ring-example.conf \
18 --bootstrap-config examples/ring_app/bootstrap.cfg
19 ```
20
21 Once _trapperkeeper_ is running, point your browser to either http://localhost:8080/ernie or http://localhost:8080/bert
22 to see the ring handlers and hit counter in action.
23
24 As you can see from the command line there are two configuration files needed to launch _trapperkeeper_.
25
26 ### The `bootstrap.cfg` file
27
28 The bootstrap config file contains a list of services that _trapperkeeper_ will load up and make available. They are
29 listed as fully-qualified Clojure namespaces and service names. For this example the bootstrap.cfg looks like this:
30
31 ```
32 puppetlabs.trapperkeeper.services.webserver.jetty9-service/jetty9-service
33 examples.ring-app.example-services/count-service
34 examples.ring-app.example-services/bert-service
35 examples.ring-app.example-services/ernie-service
36 ```
37
38 This configuration indicates the jetty9 `WebserverService` is to be loaded, as
39 well as the three new services defined in the `example_services.clj` file.
40
41 ### The `ring-example.conf` configuration file
42
43 For the application configuration, a file called `ring-example.conf` provides the most minimal
44 configuration of the webserver-service, which is simply the port the service will be listening
45 on and also a `logging-config` key which contains a path to a logback config file which defines
46 the logging configuration.
47
48 ```
49 global {
50 # Points to a logback config file
51 logging-config: examples/ring_app/logback.xml
52 }
53
54 webserver {
55 # Port to listen on for clear-text HTTP.
56 port: 8080
57 }
58 ```
59
60 ### Debug mode
61
62 There is a debugging statement inside the count-service which displays the state of the counter when it is
63 to be incremented. To turn on debugging logging pass in the `--debug` option on the command line, like so:
64
65 ```sh
66 lein trampoline run --config examples/ring_app/ring-example.conf \
67 --bootstrap-config examples/ring_app/bootstrap.cfg \
68 --debug
69 ```
70
71 When run you will see debug output any time you hit the hit-counting endpoint. This is the equivalent of setting the
72 logback root logger to `DEBUG` instead of `INFO`, and will override whatever log level the root logger is set to.
73
74 ## Defining the Services
75
76 And now, without further ado, let's look at some code!
77
78 ### Define the _hit count_ service
79
80 First we will need to define the hit counter service, which will later be used by the web services to show users which
81 visitor number they are. It is entirely expressed with this code:
82
83 ```clj
84 (def ^{:private true} hit-count (atom {}))
85
86 (defn- inc-and-get
87 "Increments the hit count for the provided endpoint and returns the new hit count."
88 [endpoint]
89 {:pre [(string? endpoint)]
90 :post [(integer? %) (> % 0)]}
91
92 (let [new-hit-counts (swap! hit-count #(assoc % endpoint (cond (contains? % endpoint)
93 (inc (% endpoint)) :else 1)))]
94
95 (log/debug "Incrementing hit count for" endpoint "from"
96 (dec (new-hit-counts endpoint)) "to" (new-hit-counts endpoint))
97
98 (new-hit-counts endpoint)))
99
100 (defprotocol CountService
101 (inc-and-get [this endpoint]))
102
103 (defservice count-service
104 "This is a simple service which simply keeps a counter. It contains one function, inc-and-get, which
105 increments the count and returns it."
106 ;; Here we specify the service's protocol
107 CountService
108 ;; This vector declares the service's dependencies on other services and their functions,
109 []
110 ;; Implement the `init` function from the `Lifecycle` protocol to
111
112 ;; initialize state:
113 (init [this context]
114 (assoc context :hit-counts (atom {})))
115 ;; Implement the inc-and-get function.
116 (inc-and-get [this endpoint]
117 (inc-and-get* ((service-context this) :hit-counts) endpoint)))
118 ```
119
120 The `defservice` macro is used to define a _trapperkeeper_ service and it is located in the
121 `puppetlabs._trapperkeeper_.core` namespace.
122
123 For more info on how the `defservice` macro works, see the
124 [`defservice` section of the trapperkeeper docs](https://github.com/puppetlabs/trapperkeeper/tree/0.3.0#defservice)
125
126 The `inc-and-get` function will keep a tally of hit counts for a provided endpoint. It
127 is later exported with the last form in the service definition which is a map of this service's function names to the
128 actual functions which do all the work.
129
130 ### Define the _bert_ service
131
132 The `bert-service` is a more interesting service which utilizes the `webserver` service to create HTTP
133 responses to requests made to specific endpoints, and is defined here:
134
135 ```clj
136 (defn- success-response
137 "Return a ring response map containing a HTTP response code of 200 (OK) and HTML which displays the hitcount on this
138 endpoint as well as all the data provided by Ring."
139 [hit-count req]
140 {:status 200
141 :body (str "<h1>Hello from http://" (:server-name req) ":" (:server-port req) (:uri req) "</h1>"
142 (if (:debug? req) "<h3>DEBUGGING ENABLED!</h3>" "")
143 "<p>You are visitor number " hit-count ".</p>"
144 "<pre>" (pprint-to-string req) "</pre>")})
145
146 (defn- ring-handler
147 "Executes the inc-and-get command and passes it into success-reponse which generates a ring response."
148 [inc-and-get endpoint req]
149 (success-response (inc-and-get endpoint) req))
150
151 (defservice bert-service
152 "This is the bert web service. The Clojure web application library, Ring, is used to create simple
153 responses to an endpoint. It depends on the count-service above to use as a primitive hit counter.
154 See https://github.com/ring-clojure/ring for documentation on Ring."
155
156 ;; This service needs functionality from the webserver service, and the count service.
157 [[:WebserverService add-ring-handler]
158 [:CountService inc-and-get]]
159
160 ;; Implement the `init` lifecycle function to register the ring handler
161 (init [this context]
162 (let [endpoint "/bert"]
163 (add-ring-handler (partial ring-handler inc-and-get endpoint) endpoint))
164 context)
165
166 (stop [this context]
167 (log/info "Bert service shutting down")
168 context))
169 ```
170
171 The general structure of this service is similar to the _hit count_ service.
172
173 Since this service requires the use of functionality from other services, the dependency list contains two
174 dependent services and the functions that are required from each. The element containing
175 `[:WebserverService add-ring-handler]` states that the `add-ring-handler` function from the
176 `:WebserverService` is needed by this service. And, of course, we also need to pull in the `inc-and-get` function
177 from the _hit count_ service previously defined. This is accomplished by the `[:CountService inc-and-get]` dependency
178 list item.
179
180 #### Ring handlers
181
182 In the body of the service definition is a call to the `add-ring-handler` function. This function takes two
183 parameters, the first being a _ring handler_ which is, essentially, a function which takes a `request` data map as a
184 single parameter and returns a map containing different parts of an HTTP response. The second parameter to
185 `add-ring-handler` is the base endpoint that the handler is attached to.
186
187 In this example, a partial function is created from the `ring-handler` function which is passed an endpoint to operate
188 on and the `inc-and-get` function from the _hit count_ service which generates the hit count.
189
190 See https://github.com/ring-clojure/ring for further documentation on the Ring API.
191
192 ### Define the _ernie_ service
193
194 The _ernie_ service is very similar to the _bert_ service, but also leverages
195 another bit of built-in _trapperkeeper_ functionality: the `:ConfigService`.
196
197 This service can be specified as a dependency, and provides functions that can be
198 used to retrieve user-specified configuration values. In this case, we've added an `example`
199 section to the `ring-example.conf` file, and specified a setting `ernie-url-prefix`
200 that can be used to control the URL prefix where the `ernie-service` will
201 be available in the web server.
202
203 The config service also provides a top-level config setting named `:debug`, which
204 is a boolean that reflects whether or not the user launched _trapperkeeper_ in
205 debug mode. In the `ernie-service` we use a simple ring middleware function
206 to inject that value into the ring request map, so that it can be checked by
207 the ring handler.
208
209 ```clj
210 (defn debug-middleware
211 "Ring middleware to add the :debug configuration value to the request map."
212 [app debug?]
213 (fn [req]
214 (app (assoc req :debug? debug?))))
215
216 (defservice ernie-service
217 "This is the ernie service which operates on the /ernie endpoint. It is essentially identical to the bert service."
218 [[:WebserverService add-ring-handler]
219 [:CountService inc-and-get]
220 [:ConfigService get-in-config]]
221
222 (init [this context]
223 (let [endpoint (get-in-config [:example :ernie-url-prefix])
224 ring-handler (-> (partial ring-handler inc-and-get endpoint)
225 (debug-middleware (get-in-config [:debug])))]
226 (add-ring-handler ring-handler endpoint))
227 context)
228
229 (stop [this context]
230 (log/info "Ernie service shutting down")
231 context))
232 ```
233
234 This means that you can change the URL of the `ernie-service` simply by editing
235 the configuration file.
236
237 ## Logging
238
239 At startup, _trapperkeeper_ will configure the logging system based on a logback configuration
240 file. This means that your services can all just dive run it and call the
241 logging functions available in `clojure.tools.logging` without worrying about configuration.
242
243 ### The `logback.xml` file
244
245 A minimal `logback.xml` file is provided in this example to demonstrate how to configure logging in
246 _trapperkeeper_.
247
248 ```xml
249 <configuration scan="true">
250 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
251 <encoder>
252 <pattern>%d %-5p [%c{2}] %m%n</pattern>
253 </encoder>
254 </appender>
255
256 <root level="info">
257 <appender-ref ref="STDOUT" />
258 </root>
259 </configuration>
260 ```
261
262 See http://logback.qos.ch/manual/configuration.html for documentation on how to configure logback.
0 puppetlabs.trapperkeeper.services.nrepl.nrepl-service/nrepl-service
1 puppetlabs.trapperkeeper.services.webserver.jetty9-service/jetty9-service
2 examples.ring-app.example-services/count-service
3 examples.ring-app.example-services/bert-service
4 examples.ring-app.example-services/ernie-service
0 <configuration scan="true">
1 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
2 <encoder>
3 <pattern>%d %-5p [%c{2}] %m%n</pattern>
4 </encoder>
5 </appender>
6
7 <root level="info">
8 <appender-ref ref="STDOUT" />
9 </root>
10 </configuration>
0 global: {
1 # Points to a logback config file
2 logging-config: examples/ring_app/logback.xml
3 }
4
5 nrepl: {
6 enabled: true
7 }
8
9 webserver: {
10 # Port to listen on for clear-text HTTP.
11 port: 8080
12 }
13
14 example: {
15 # The URL prefix at which the ernie service should be available in the web server.
16 ernie-url-prefix: /ernie
17 }
18
0 (ns examples.ring-app.example-services
1 (:import (clojure.lang Atom))
2 (:require [clojure.tools.logging :as log]
3 [puppetlabs.kitchensink.core :refer [pprint-to-string]]
4 [puppetlabs.trapperkeeper.core :refer [defservice]]
5 [puppetlabs.trapperkeeper.services :refer [service-context]]))
6
7 (defn- inc-and-get*
8 "Increments the hit count for the provided endpoint and returns the new hit count."
9 [hit-counts endpoint]
10 {:pre [(instance? Atom hit-counts)
11 (string? endpoint)]
12 :post [(integer? %) (> % 0)]}
13
14 (let [new-hit-counts (swap! hit-counts update-in [endpoint] (fnil inc 0))]
15 (log/debug "Incrementing hit count for" endpoint "from"
16 (dec (new-hit-counts endpoint)) "to" (new-hit-counts endpoint))
17
18 (new-hit-counts endpoint)))
19
20 (defprotocol CountService
21 (inc-and-get [this endpoint]))
22
23 (defservice count-service
24 "This is a simple service which simply keeps a counter. It contains one function, inc-and-get, which
25 increments the count and returns it."
26 ;; Here we specify the service's protocol
27 CountService
28 ;; This vector declares the service's dependencies on other services and their functions,
29 []
30 ;; Implement the `init` function from the `Lifecycle` protocol to
31 ;; initialize state:
32 (init [this context]
33 (assoc context :hit-counts (atom {})))
34 ;; Implement the inc-and-get function.
35 (inc-and-get [this endpoint]
36 (inc-and-get* ((service-context this) :hit-counts) endpoint)))
37
38 (defn- success-response
39 "Return a ring response map containing a HTTP response code of 200 (OK) and HTML which displays the hitcount on this
40 endpoint as well as all the data provided by Ring."
41 [hit-count req]
42 {:status 200
43 :body (str "<h1>Hello from http://" (:server-name req) ":" (:server-port req) (:uri req) "</h1>"
44 (if (:debug? req) "<h3>DEBUGGING ENABLED!</h3>" "")
45 "<p>You are visitor number " hit-count ".</p>"
46 "<pre>" (pprint-to-string req) "</pre>")})
47
48 (defn- ring-handler
49 "Executes the inc-and-get command and passes it into success-reponse which generates a ring response."
50 [inc-and-get endpoint req]
51 (success-response (inc-and-get endpoint) req))
52
53 (defservice bert-service
54 "This is the bert web service. The Clojure web application library, Ring, is used to create simple
55 responses to an endpoint. It depends on the count-service above to use as a primitive hit counter.
56 See https://github.com/ring-clojure/ring for documentation on Ring."
57
58 ;; This service needs functionality from the webserver service, and the count service.
59 [[:WebserverService add-ring-handler]
60 [:CountService inc-and-get]]
61
62 ;; Implement the `init` lifecycle function to register the ring handler
63 (init [this context]
64 (let [endpoint "/bert"]
65 (add-ring-handler (partial ring-handler inc-and-get endpoint) endpoint))
66 context)
67
68 (stop [this context]
69 (log/info "Bert service shutting down")
70 context))
71
72 (defn debug-middleware
73 "Ring middleware to add the :debug configuration value to the request map."
74 [app debug?]
75 (fn [req]
76 (app (assoc req :debug? debug?))))
77
78 (defservice ernie-service
79 "This is the ernie service which operates on the /ernie endpoint. It is essentially identical to the bert service."
80 [[:WebserverService add-ring-handler]
81 [:CountService inc-and-get]
82 [:ConfigService get-in-config]]
83
84 (init [this context]
85 (let [endpoint (get-in-config [:example :ernie-url-prefix])
86 ring-handler (-> (partial ring-handler inc-and-get endpoint)
87 (debug-middleware (get-in-config [:debug])))]
88 (add-ring-handler ring-handler endpoint))
89 context)
90
91 (stop [this context]
92 (log/info "Ernie service shutting down")
93 context))
0 (ns examples.ring-app.repl
1 (:require [puppetlabs.trapperkeeper.services.webserver.jetty9-service
2 :refer [jetty9-service]]
3 [examples.ring-app.example-services
4 :refer [count-service bert-service ernie-service]]
5 [puppetlabs.trapperkeeper.core :as tk]
6 [puppetlabs.trapperkeeper.app :as tka]
7 [clojure.tools.namespace.repl :refer (refresh)]))
8
9 ;; This namespace shows an example of the "reloaded" clojure workflow
10 ;; ( http://thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded )
11 ;;
12 ;; It's based on the pattern from Stuart Sierra's `Component` library:
13 ;; ( https://github.com/stuartsierra/component#reloading )
14 ;;
15 ;; You can load this namespace up into a REPL and then run `(go)` to boot
16 ;; and run the sample application. Then, you can run `(reset)` at any time
17 ;; to stop the running app, reload all of the necessary namespaces, and start
18 ;; a new instance of the app. This means that you can do iterative development
19 ;; without having to restart the whole JVM.
20 ;;
21 ;; You can also view the context of the application (and all of the
22 ;; trapperkeeper services) via `(context)` (or pretty-printed with
23 ;; `print-context`).
24
25 (def system nil)
26
27 (defn init []
28 (alter-var-root #'system
29 (fn [_] (tk/build-app
30 [jetty9-service
31 count-service
32 bert-service
33 ernie-service]
34 {:global
35 {:logging-config "examples/ring_app/logback.xml"}
36 :webserver {:port 8080 }
37 :example {:ernie-url-prefix
38 "/ernie"}})))
39 (alter-var-root #'system tka/init)
40 (tka/check-for-errors! system))
41
42 (defn start []
43 (alter-var-root #'system
44 (fn [s] (if s (tka/start s))))
45 (tka/check-for-errors! system))
46
47 (defn stop []
48 (alter-var-root #'system
49 (fn [s] (if s (tka/stop s)))))
50
51 (defn go []
52 (init)
53 (start))
54
55 (defn context []
56 @(tka/app-context system))
57
58 (defn print-context []
59 (clojure.pprint/pprint (context)))
60
61 (defn reset []
62 (stop)
63 (refresh :after 'examples.ring-app.repl/go))
0 Sample Trapperkeeper Servlet Web App
1 ---------------------------------
2
3 To run the app, use this command:
4
5 ```sh
6 lein trampoline run --config examples/servlet_app/servlet-example.conf \
7 --bootstrap-config examples/servlet_app/bootstrap.cfg
8
9 ```
0 puppetlabs.trapperkeeper.services.webserver.jetty9-service/jetty9-service
1 examples.servlet-app.servlet-app/hello-servlet-service
0 <configuration scan="true">
1 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
2 <encoder>
3 <pattern>%d %-5p [%c{2}] %m%n</pattern>
4 </encoder>
5 </appender>
6
7 <root level="info">
8 <appender-ref ref="STDOUT" />
9 </root>
10 </configuration>
0 global: {
1 # Points to a logback config file
2 logging-config: examples/servlet_app/logback.xml
3 }
4
5 webserver: {
6 host: 0.0.0.0
7 port: 8080
8 }
0 (ns examples.servlet-app.servlet-app
1 (:import [examples.servlet_app MyServlet])
2 (:require [puppetlabs.trapperkeeper.core :refer [defservice]]
3 [clojure.tools.logging :as log]))
4
5 (defservice hello-servlet-service
6 [[:WebserverService add-servlet-handler]]
7 (init [this context]
8 (log/info "Initializing hello-servlet-service")
9 (add-servlet-handler (MyServlet. "Hi there!") "/hello")
10 (add-servlet-handler (MyServlet. "See you later!") "/goodbye")
11 context)
12 (stop [this context]
13 (log/info "Shutting down hello-servlet-service")
14 context))
15
0 package examples.servlet_app;
1
2 import java.io.IOException;
3 import javax.servlet.ServletException;
4 import javax.servlet.http.HttpServlet;
5 import javax.servlet.http.HttpServletRequest;
6 import javax.servlet.http.HttpServletResponse;
7
8 public class MyServlet extends HttpServlet {
9 private String message;
10
11 public MyServlet(String message) {
12 this.message = message;
13 }
14
15 @Override
16 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
17 response.setContentType("text/html");
18 response.setStatus(HttpServletResponse.SC_OK);
19 response.getWriter().println(message);
20 }
21 }
0 Sample Trapperkeeper WAR Web App
1 ---------------------------------
2
3 To run the app, use this command:
4
5 ```sh
6 lein trampoline run --config examples/war_app/war-example.conf \
7 --bootstrap-config examples/war_app/bootstrap.cfg
8
9 ```
10
11 Open
12
13 ```
14 http://localhost:8080/test/hello
15
16 ```
17
18 in your browser to see the famous Hello World message.
0 puppetlabs.trapperkeeper.services.webserver.jetty9-service/jetty9-service
1 examples.war-app.war-app/hello-webservice
0 <configuration scan="true">
1 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
2 <encoder>
3 <pattern>%d %-5p [%c{2}] %m%n</pattern>
4 </encoder>
5 </appender>
6
7 <root level="info">
8 <appender-ref ref="STDOUT" />
9 </root>
10 </configuration>
0 (ns examples.war-app.war-app
1 (:require [puppetlabs.trapperkeeper.core :refer [defservice]]
2 [clojure.tools.logging :as log]))
3
4 (defservice hello-webservice
5 [[:WebserverService add-war-handler]]
6 (init [this context]
7 (log/info "Initializing hello web service")
8 (add-war-handler "dev-resources/helloWorld.war" "/test")
9 context)
10 (stop [this context]
11 (log/info "Shutting down hello web service")
12 context))
0 global: {
1 # Points to a logback configuration file
2 logging-config: examples/war_app/logback.xml
3 }
4
5 webserver: {
6 host: 0.0.0.0
7 port: 8080
8 }
0 Sample Trapperkeeper Multiserver Web App
1 -----------------------------------------
2
3 To run the app, use this command:
4
5 ```sh
6 lein trampoline run --config examples/webrouting_app/webrouting-example.conf \
7 --bootstrap-config examples/webrouting_app/bootstrap.cfg
8
9 ```
10
11 Open any of
12
13 ```
14 http://localhost:8080/foo
15 http://localhost:8080/bar
16 http://localhost:8080/baz
17 http://localhost:8080/goodbye
18 http://localhost:9000/quux
19 http://localhost:9000/bert
20
21 ```
22
23 in your browser to see the famous Hello World message.
24
25 Open
26
27 ```
28 http://localhost:8080/hello/[string]
29
30 ```
31 where [string] is any string of your choosing to see a Hello message specific for
32 that string.
0 puppetlabs.trapperkeeper.services.nrepl.nrepl-service/nrepl-service
1 puppetlabs.trapperkeeper.services.webserver.jetty9-service/jetty9-service
2 puppetlabs.trapperkeeper.services.webrouting.webrouting-service/webrouting-service
3 examples.webrouting-app.example-services/foo-service
4 examples.webrouting-app.example-services/bar-service
5 examples.webrouting-app.example-services/quux-service
6 examples.webrouting-app.example-services/bert-service
7 examples.webrouting-app.example-services/hello-service
0 <configuration scan="true">
1 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
2 <encoder>
3 <pattern>%d %-5p [%c{2}] %m%n</pattern>
4 </encoder>
5 </appender>
6
7 <root level="info">
8 <appender-ref ref="STDOUT" />
9 </root>
10 </configuration>
0 (ns examples.webrouting-app.example-services
1 (:require [clojure.tools.logging :as log]
2 [puppetlabs.trapperkeeper.core :refer [defservice]]
3 [puppetlabs.trapperkeeper.services :refer [get-services]]
4 [compojure.core :as compojure]
5 [compojure.route :as route]))
6
7 (defn hello-world-app
8 [req]
9 {:status 200
10 :headers {"Content-Type" "text/plain"}
11 :body "Hello, World!"})
12
13 (defn hello-app
14 []
15 (compojure/routes
16 (compojure/GET "/:caller" [caller]
17 (fn [req]
18 (log/info "Handling request for caller:" caller)
19 {:status 200
20 :headers {"Content-Type" "text/plain"}
21 :body (format "Hello, %s!" caller)}))
22 (route/not-found "Not Found")))
23
24 (defservice foo-service
25 [[:WebroutingService add-ring-handler]]
26 (init [this context]
27 (log/info "Initializing foo service")
28 (add-ring-handler this hello-world-app)
29 context))
30
31 (defservice bar-service
32 [[:WebroutingService add-ring-handler]]
33 (init [this context]
34 (log/info "Initializing bar service")
35 (add-ring-handler this hello-world-app {:route-id :bar})
36 (add-ring-handler this hello-world-app {:route-id :baz})
37 context))
38
39 (defservice quux-service
40 [[:WebroutingService add-ring-handler]]
41 (init [this context]
42 (log/info "Initializing quux service")
43 (add-ring-handler this hello-world-app)
44 context))
45
46 (defservice bert-service
47 [[:WebroutingService add-ring-handler]]
48 (init [this context]
49 (log/info "Initializing bert service")
50 (add-ring-handler this hello-world-app {:route-id :baz})
51 (add-ring-handler this hello-world-app {:route-id :bert})
52 context))
53
54 (defservice hello-service
55 [[:WebroutingService add-ring-handler get-route]]
56 (init [this context]
57 (log/info "Initializing hello service")
58 (let [url-prefix (get-route this)]
59 (add-ring-handler
60 this
61 (compojure/context url-prefix []
62 (hello-app))))
63 context))
0 global: {
1 # Points to a logback config file
2 logging-config: examples/webrouting_app/logback.xml
3 }
4
5 nrepl: {
6 enabled: true
7 }
8
9 webserver: {
10 foo: {
11 port: 8080
12 default-server: true
13 }
14 quux: {
15 port: 9000
16 }
17 }
18
19 web-router-service: {
20 "examples.webrouting-app.example-services/foo-service": "/foo"
21 "examples.webrouting-app.example-services/bar-service": {
22 bar: "/bar"
23 baz: "/goodbye"
24 }
25 "examples.webrouting-app.example-services/quux-service": {
26 route: "/quux"
27 server: "quux"
28 }
29 "examples.webrouting-app.example-services/bert-service": {
30 baz: "/baz"
31 bert: {
32 route: "/bert"
33 server: "quux"
34 }
35 }
36 "examples.webrouting-app.example-services/hello-service": "/hello"
37 }
0 #!/bin/bash
1
2 lein2 test
0 package com.puppetlabs.trapperkeeper.services.webserver.jetty9.utils;
1
2 import javax.servlet.http.HttpServletRequest;
3 import javax.servlet.http.HttpServletRequestWrapper;
4
5 /**
6 * This class provides a wrapper for an existing HttpServletRequest object
7 * which returns an alternate request URI from the one that the injected
8 * HttpServletRequest object would return when its getRequestURI() method
9 * is called.
10 */
11 public class HttpServletRequestWithAlternateRequestUri
12 extends HttpServletRequestWrapper {
13
14 private String requestUri;
15
16 public HttpServletRequestWithAlternateRequestUri(
17 HttpServletRequest request,
18 String requestUri) {
19 super(request);
20 this.requestUri = requestUri;
21 }
22
23 @Override
24 public String getRequestURI() {
25 return requestUri;
26 }
27 }
0 #!/usr/bin/env bash
1
2 set -e
3 set -x
4
5 git fetch --tags
6
7 lein test
8 echo "Tests passed!"
9
10 lein release
11 echo "Release plugin successful, pushing changes to git"
12
13 git push origin --tags HEAD:$TK_JETTY9_BRANCH
14
15 echo "git push successful."
0 # SOME DESCRIPTIVE TITLE.
1 # Copyright (C) YEAR Puppet <docs@puppet.com>
2 # This file is distributed under the same license as the puppetlabs.trapperkeeper_webserver_jetty9 package.
3 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
4 #
5 #, fuzzy
6 msgid ""
7 msgstr ""
8 "Project-Id-Version: puppetlabs.trapperkeeper_webserver_jetty9 \n"
9 "Report-Msgid-Bugs-To: docs@puppet.com\n"
10 "POT-Creation-Date: \n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language: \n"
15 "MIME-Version: 1.0\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
17 "Content-Transfer-Encoding: 8bit\n"
18
19 #: src/puppetlabs/trapperkeeper/services/webrouting/webrouting_service_core.clj
20 msgid "service {0} does not appear in configuration"
21 msgstr ""
22
23 #: src/puppetlabs/trapperkeeper/services/webrouting/webrouting_service_core.clj
24 msgid "endpoint with id {0} does not appear in configuration for service {1}"
25 msgstr ""
26
27 #: src/puppetlabs/trapperkeeper/services/webrouting/webrouting_service_core.clj
28 msgid "no route-id specified for a service with multiple routes"
29 msgstr ""
30
31 #: src/puppetlabs/trapperkeeper/services/webserver/experimental/jetty9_websockets.clj
32 msgid "No handler defined for websocket event ''{0}'' with args: ''{1}''"
33 msgstr ""
34
35 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
36 msgid ""
37 "Found SSL config options: {0}; If configuring SSL from PEM files, you must "
38 "provide all of the following options: {1}"
39 msgstr ""
40
41 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
42 msgid "Unable to open ''ssl-cert'' file: {0}"
43 msgstr ""
44
45 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
46 msgid "No certs found in ''ssl-cert'' file: {0}"
47 msgstr ""
48
49 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
50 msgid "Unable to open ''ssl-cert-chain'' file: {0}"
51 msgstr ""
52
53 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
54 msgid ""
55 "Found settings for both keystore-based and PEM-based SSL; using PEM-based "
56 "settings, ignoring {0}"
57 msgstr ""
58
59 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
60 msgid ""
61 "Missing some SSL configuration; must provide either :ssl-cert, :ssl-key, "
62 "and :ssl-ca-cert, OR :truststore, :trust-password, :keystore, and :key-"
63 "password."
64 msgstr ""
65
66 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
67 msgid ""
68 "Unexpected value found for client auth config option: {0}. Expected need, "
69 "want, or none."
70 msgstr ""
71
72 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
73 msgid "Non-readable path specified for ssl-crl-path option: {0}"
74 msgstr ""
75
76 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
77 msgid "Error: More than one default server specified in configuration"
78 msgstr ""
79
80 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
81 msgid ""
82 "Either host, port, ssl-host, or ssl-port must be specified on the config in "
83 "order for the server to be started"
84 msgstr ""
85
86 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
87 msgid ""
88 "The ''post-config-script'' setting is for advanced use cases only, and may "
89 "be subject to minor changes when the application is upgraded."
90 msgstr ""
91
92 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_config.clj
93 msgid "Invalid script string in webserver ''post-config-script'' configuration"
94 msgstr ""
95
96 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
97 msgid "Removing buggy security provider {0}"
98 msgstr ""
99
100 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
101 msgid "Could not remove security providers; HTTPS may not work!"
102 msgstr ""
103
104 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
105 msgid "webserver config overridden for key ''{0}''"
106 msgstr ""
107
108 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
109 msgid ""
110 "`ssl-protocols` contains SSLv3, a protocol with known vulnerabilities; we "
111 "recommend removing it from the `ssl-protocols` list"
112 msgstr ""
113
114 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
115 msgid "{0} proxying failed"
116 msgstr ""
117
118 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
119 msgid "{0} could not close the connection"
120 msgstr ""
121
122 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
123 msgid "Cleaning up JMX MBean container"
124 msgstr ""
125
126 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
127 msgid "Shutting down web server."
128 msgstr ""
129
130 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
131 msgid ""
132 "Web server failed to shut down gracefully in configured timeout period "
133 "({0}); cancelling remaining requests."
134 msgstr ""
135
136 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
137 msgid "Web server shutdown"
138 msgstr ""
139
140 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
141 msgid "Starting web server."
142 msgstr ""
143
144 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
145 msgid "Encountered error starting web server, so shutting down"
146 msgstr ""
147
148 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
149 msgid ""
150 "overrides cannot be set because webserver has already processed the config"
151 msgstr ""
152
153 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
154 msgid ""
155 "overrides cannot be set because they have already been set and webserver has "
156 "already processed the config"
157 msgstr ""
158
159 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
160 msgid "overrides cannot be set because they have already been set"
161 msgstr ""
162
163 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_core.clj
164 msgid ""
165 "no server-id was specified for this operation and no default server was "
166 "specified in the configuration"
167 msgstr ""
168
169 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_service.clj
170 msgid "Initializing web server(s)."
171 msgstr ""
172
173 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_service.clj
174 msgid "Starting web server(s)."
175 msgstr ""
176
177 #: src/puppetlabs/trapperkeeper/services/webserver/jetty9_service.clj
178 msgid "Shutting down web server(s)."
179 msgstr ""
180
181 #: src/puppetlabs/trapperkeeper/services/webserver/normalized_uri_helpers.clj
182 msgid "Invalid relative path (.. or .) in: {0}"
183 msgstr ""
0 (def jetty-version "9.2.10.v20150310")
1
2 (defproject puppetlabs/trapperkeeper-webserver-jetty9 "1.7.0"
3 :description "A jetty9-based webserver implementation for use with the puppetlabs/trapperkeeper service framework."
4 :url "https://github.com/puppetlabs/trapperkeeper-webserver-jetty9"
5 :license {:name "Apache License, Version 2.0"
6 :url "http://www.apache.org/licenses/LICENSE-2.0"}
7
8 :min-lein-version "2.7.1"
9
10 :parent-project {:coords [puppetlabs/clj-parent "0.1.3"]
11 :inherit [:managed-dependencies]}
12
13 ;; Abort when version ranges or version conflicts are detected in
14 ;; dependencies. Also supports :warn to simply emit warnings.
15 ;; requires lein 2.2.0+.
16 :pedantic? :abort
17 :dependencies [[org.clojure/clojure]
18 [org.clojure/java.jmx]
19 [org.clojure/tools.logging]
20
21 [org.codehaus.janino/janino]
22
23 [javax.servlet/javax.servlet-api "3.1.0"]
24 ;; Jetty Webserver
25 [org.eclipse.jetty/jetty-server ~jetty-version
26 :exclusions [org.eclipse.jetty.orbit/javax.servlet]]
27 [org.eclipse.jetty/jetty-servlet ~jetty-version]
28 [org.eclipse.jetty/jetty-servlets ~jetty-version]
29 [org.eclipse.jetty/jetty-webapp ~jetty-version]
30 [org.eclipse.jetty/jetty-proxy ~jetty-version]
31 [org.eclipse.jetty/jetty-jmx ~jetty-version]
32 [org.eclipse.jetty.websocket/websocket-server ~jetty-version]
33
34 [prismatic/schema]
35 [ring/ring-servlet]
36 [ring/ring-codec]
37
38 [puppetlabs/ssl-utils]
39 [puppetlabs/kitchensink]
40 [puppetlabs/trapperkeeper]
41 [puppetlabs/i18n]
42 ]
43
44 :source-paths ["src"]
45 :java-source-paths ["java"]
46
47 :plugins [[lein-parent "0.3.1"]
48 [puppetlabs/i18n "0.4.3"]]
49
50 :deploy-repositories [["releases" {:url "https://clojars.org/repo"
51 :username :env/clojars_jenkins_username
52 :password :env/clojars_jenkins_password
53 :sign-releases false}]]
54
55 ;; By declaring a classifier here and a corresponding profile below we'll get an additional jar
56 ;; during `lein jar` that has all the code in the test/ directory. Downstream projects can then
57 ;; depend on this test jar using a :classifier in their :dependencies to reuse the test utility
58 ;; code that we have.
59 :classifiers [["test" :testutils]]
60
61 :test-paths ["test/clj"]
62
63 :profiles {:dev {:source-paths ["examples/multiserver_app/src"
64 "examples/ring_app/src"
65 "examples/servlet_app/src/clj"
66 "examples/war_app/src"
67 "examples/webrouting_app/src"]
68 :java-source-paths ["examples/servlet_app/src/java"
69 "test/java"]
70 :dependencies [[puppetlabs/http-client]
71 [puppetlabs/kitchensink nil :classifier "test"]
72 [puppetlabs/trapperkeeper nil :classifier "test"]
73 [org.clojure/tools.namespace]
74 [compojure]
75 [stylefruits/gniazdo "0.4.0" :exclusions [org.eclipse.jetty.websocket/websocket-api
76 org.eclipse.jetty.websocket/websocket-client
77 org.eclipse.jetty/jetty-util]]
78 [ring/ring-core]]
79 ;; Enable SSLv3 for unit tests that exercise SSLv3
80 :jvm-opts ["-Djava.security.properties=./dev-resources/java.security"]}
81
82 :testutils {:source-paths ^:replace ["test/clj"]
83 :java-source-paths ^:replace ["test/java"]}}
84
85 :main puppetlabs.trapperkeeper.main
86 )
0 (ns puppetlabs.experimental.websockets.client)
1
2 (defprotocol WebSocketProtocol
3 "Functions to manage the lifecycle of a websocket session"
4 (idle-timeout! [this ms]
5 "Set the idle timeout for the session, in milliseconds")
6 (connected? [this]
7 "Returns a boolean indicating if the session is currently connected")
8 (send! [this msg]
9 "Send a message to the websocket client")
10 (close! [this] [this code reason]
11 "Close the websocket session.")
12 (remote-addr [this]
13 "Find the remote address of a websocket client")
14 (ssl? [this]
15 "Returns a boolean indicating if the session was established by wss://")
16 (peer-certs [this]
17 "Returns an array of X509Certs presented by the ssl peer, if any")
18 (request-path [this]
19 "Returns the URI path used in the websocket upgrade request to the server"))
0 (ns puppetlabs.trapperkeeper.services.webrouting.webrouting-service
1 (:require
2 [clojure.tools.logging :as log]
3
4 [puppetlabs.trapperkeeper.services :refer [service-context]]
5 [puppetlabs.trapperkeeper.services.webrouting.webrouting-service-core :as core]
6 [puppetlabs.trapperkeeper.core :refer [defservice]]
7 [schema.core :as schema]))
8
9 (defprotocol WebroutingService
10 (get-route [this svc] [this svc route-id])
11 (get-server [this svc] [this svc route-id])
12 (add-context-handler [this svc context-path] [this svc context-path options])
13 (add-ring-handler [this svc handler] [this svc handler options])
14 (add-servlet-handler [this svc servlet] [this svc servlet options])
15 (add-websocket-handler [this svc handlers] [this svc handlers options])
16 (add-war-handler [this svc war] [this svc war options])
17 (add-proxy-route [this svc target] [this svc target options])
18 (override-webserver-settings! [this overrides] [this server-id overrides])
19 (get-registered-endpoints [this] [this server-id])
20 (log-registered-endpoints [this] [this server-id])
21 (join [this] [this server-id]))
22
23 (defservice webrouting-service
24 "Provides the ability to route handlers to different jetty9 webserver services"
25 WebroutingService
26 [WebserverService
27 [:ConfigService get-in-config]]
28 (init [this context]
29 (let [config (get-in-config [:web-router-service])]
30 (when (nil? config)
31 (throw (IllegalArgumentException.
32 ":web-router-service section of configuration not present")))
33 (core/init context config)))
34
35 (get-route [this svc]
36 (core/get-route (service-context this) svc nil))
37
38 (get-route [this svc route-id]
39 (core/get-route (service-context this) svc route-id))
40
41 (get-server [this svc]
42 (core/get-server (service-context this) svc nil))
43
44 (get-server [this svc route-id]
45 (core/get-server (service-context this) svc route-id))
46
47 (add-context-handler [this svc base-path]
48 (core/add-context-handler! (service-context this)
49 WebserverService svc
50 base-path {}))
51
52 (add-context-handler [this svc base-path options]
53 (core/add-context-handler! (service-context this)
54 WebserverService svc
55 base-path options))
56
57 (add-ring-handler [this svc handler]
58 (core/add-ring-handler! (service-context this)
59 WebserverService svc
60 handler {}))
61
62 (add-ring-handler [this svc handler options]
63 (core/add-ring-handler! (service-context this)
64 WebserverService svc
65 handler options))
66
67 (add-servlet-handler [this svc servlet]
68 (core/add-servlet-handler! (service-context this)
69 WebserverService svc
70 servlet {}))
71
72 (add-servlet-handler [this svc servlet options]
73 (core/add-servlet-handler! (service-context this)
74 WebserverService svc
75 servlet options))
76
77 (add-websocket-handler [this svc handlers]
78 (core/add-websocket-handler! (service-context this)
79 WebserverService svc
80 handlers {}))
81
82 (add-websocket-handler [this svc handlers options]
83 (core/add-websocket-handler! (service-context this)
84 WebserverService svc
85 handlers options))
86
87 (add-war-handler [this svc war]
88 (core/add-war-handler! (service-context this)
89 WebserverService svc
90 war {}))
91
92 (add-war-handler [this svc war options]
93 (core/add-war-handler! (service-context this)
94 WebserverService svc
95 war options))
96
97 (add-proxy-route [this svc target]
98 (core/add-proxy-route! (service-context this)
99 WebserverService svc
100 target {}))
101
102 (add-proxy-route [this svc target options]
103 (core/add-proxy-route! (service-context this)
104 WebserverService svc
105 target options))
106
107 (override-webserver-settings! [this overrides]
108 (let [override-webserver-settings
109 (:override-webserver-settings! WebserverService)]
110 (override-webserver-settings overrides)))
111
112 (override-webserver-settings! [this server-id overrides]
113 (let [override-webserver-settings
114 (:override-webserver-settings! WebserverService)]
115 (override-webserver-settings server-id overrides)))
116
117 (get-registered-endpoints [this]
118 (let [get-registered-endpoints
119 (:get-registered-endpoints WebserverService)]
120 (get-registered-endpoints)))
121
122 (get-registered-endpoints [this server-id]
123 (let [get-registered-endpoints
124 (:get-registered-endpoints WebserverService)]
125 (get-registered-endpoints server-id)))
126
127 (log-registered-endpoints [this]
128 (let [log-registered-endpoints
129 (:log-registered-endpoints WebserverService)]
130 (log-registered-endpoints)))
131
132 (log-registered-endpoints [this server-id]
133 (let [log-registered-endpoints
134 (:log-registered-endpoints WebserverService)]
135 (log-registered-endpoints server-id)))
136
137 (join [this]
138 (let [join (:join WebserverService)]
139 (join)))
140
141 (join [this server-id]
142 (let [join (:join WebserverService)]
143 (join server-id))))
0 (ns puppetlabs.trapperkeeper.services.webrouting.webrouting-service-core
1 (:require [schema.core :as schema]
2 [puppetlabs.trapperkeeper.services.webserver.jetty9-core :as jetty9-core]
3 [puppetlabs.trapperkeeper.services :as tk-services]
4 [puppetlabs.i18n.core :as i18n]))
5
6 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
7 ;;; Schemas
8
9 (def RouteWithServerConfig
10 {:route schema/Str
11 :server schema/Str})
12
13 (def WebroutingMultipleConfig
14 {schema/Keyword (schema/either schema/Str RouteWithServerConfig)})
15
16 (def WebroutingServiceConfig
17 {schema/Keyword (schema/either schema/Str RouteWithServerConfig WebroutingMultipleConfig)})
18
19 (def RouteOption
20 {(schema/optional-key :route-id) schema/Keyword})
21
22 (def CommonOptions
23 (dissoc (merge jetty9-core/CommonOptions RouteOption) :server-id))
24
25 (def ContextHandlerOptions
26 (dissoc (merge jetty9-core/ContextHandlerOptions RouteOption) :server-id))
27
28 (def ServletHandlerOptions
29 (dissoc (merge jetty9-core/ServletHandlerOptions RouteOption) :server-id))
30
31 (def ProxyRouteOptions
32 (dissoc (merge jetty9-core/ProxyOptions RouteOption) :server-id))
33
34 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
35 ;;; Private Utility Functions
36
37 (defn get-endpoint-and-server-from-config
38 [context svc route-id]
39 (let [config (:web-router-service context)
40 no-route-id? (nil? route-id)
41 multi-route? (> (count (keys (get-in config [svc]))) 1)
42 route-id (if no-route-id?
43 :default
44 route-id)
45 endpoint (get-in config [svc route-id])
46 no-service? (nil? (get config svc))
47 no-endpoint? (nil? endpoint)
48 no-server? (nil? (schema/check schema/Str endpoint))
49 server? (nil? (schema/check RouteWithServerConfig endpoint))]
50 (cond
51 no-service? (throw
52 (IllegalArgumentException.
53 (i18n/trs "service {0} does not appear in configuration" svc)))
54 no-endpoint? (throw
55 (IllegalArgumentException.
56 (i18n/trs "endpoint with id {0} does not appear in configuration for service {1}"
57 route-id
58 svc)))
59 (and no-route-id? multi-route?)
60 (throw
61 (IllegalArgumentException.
62 (i18n/trs "no route-id specified for a service with multiple routes")))
63 no-server? {:route endpoint :server nil}
64 server? endpoint)))
65
66 (defn compute-common-elements
67 [context svc options]
68 (let [svc-id (keyword (tk-services/service-symbol svc))
69 route-id (:route-id options)
70 route-and-server (get-endpoint-and-server-from-config context svc-id route-id)
71 path (:route route-and-server)
72 server (keyword (:server route-and-server))
73 options (dissoc options :route-id)
74 opts (if (nil? server)
75 options
76 (assoc options :server-id server))]
77 {:path path :opts opts}))
78
79 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
80 ;;; Lifecycle implementations
81
82 (schema/defn ^:always-validate init
83 [context config :- WebroutingServiceConfig]
84 (let [configuration (into {} (for [[svc svc-config] config]
85 (cond
86 (nil? (schema/check (schema/either schema/Str RouteWithServerConfig) svc-config))
87 [svc {:default svc-config}]
88 (nil? (schema/check WebroutingMultipleConfig svc-config))
89 [svc svc-config])))]
90 (assoc context :web-router-service configuration)))
91
92 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
93 ;;; Service function implementations
94
95 (defn get-route
96 [context svc route-id]
97 (let [svc-id (keyword (tk-services/service-symbol svc))
98 endpoint-and-server (get-endpoint-and-server-from-config context
99 svc-id
100 route-id)]
101 (:route endpoint-and-server)))
102
103 (schema/defn ^:always-validate get-server :- (schema/maybe schema/Str)
104 [context
105 svc :- (schema/protocol tk-services/Service)
106 route-id]
107 (let [svc-id (keyword (tk-services/service-symbol svc))
108 endpoint-and-server (get-endpoint-and-server-from-config context
109 svc-id
110 route-id)]
111 (:server endpoint-and-server)))
112
113 (schema/defn ^:always-validate add-context-handler!
114 [context webserver-service
115 svc :- (schema/protocol tk-services/Service)
116 base-path
117 options :- ContextHandlerOptions]
118 (let [{:keys [path opts]} (compute-common-elements context svc options)
119 add-context-handler (:add-context-handler webserver-service)]
120 (add-context-handler base-path path opts)))
121
122 (schema/defn ^:always-validate add-ring-handler!
123 [context webserver-service
124 svc :- (schema/protocol tk-services/Service)
125 handler options :- CommonOptions]
126 (let [{:keys [path opts]} (compute-common-elements context svc options)
127 add-ring-handler (:add-ring-handler webserver-service)]
128 (add-ring-handler handler path opts)))
129
130 (schema/defn ^:always-validate add-servlet-handler!
131 [context webserver-service
132 svc :- (schema/protocol tk-services/Service)
133 servlet options :- ServletHandlerOptions]
134 (let [{:keys [path opts]} (compute-common-elements context svc options)
135 add-servlet-handler (:add-servlet-handler webserver-service)]
136 (add-servlet-handler servlet path opts)))
137
138 (schema/defn ^:always-validate add-websocket-handler!
139 [context webserver-service
140 svc :- (schema/protocol tk-services/Service)
141 handlers options :- CommonOptions]
142 (let [{:keys [path opts]} (compute-common-elements context svc options)
143 add-websocket-handler (:add-websocket-handler webserver-service)]
144 (add-websocket-handler handlers path opts)))
145
146 (schema/defn ^:always-validate add-war-handler!
147 [context webserver-service
148 svc :- (schema/protocol tk-services/Service)
149 war options :- RouteOption]
150 (let [{:keys [path opts]} (compute-common-elements context svc options)
151 add-war-handler (:add-war-handler webserver-service)]
152 (add-war-handler war path opts)))
153
154 (schema/defn ^:always-validate add-proxy-route!
155 [context webserver-service
156 svc :- (schema/protocol tk-services/Service)
157 target options :- ProxyRouteOptions]
158 (let [{:keys [path opts]} (compute-common-elements context svc options)
159 add-proxy-route (:add-proxy-route webserver-service)]
160 (add-proxy-route target path opts)))
0 (ns puppetlabs.trapperkeeper.services.webserver.experimental.jetty9-websockets
1 (:import (clojure.lang IFn)
2 (org.eclipse.jetty.server Request)
3 (org.eclipse.jetty.websocket.api WebSocketAdapter Session)
4 (org.eclipse.jetty.websocket.server WebSocketHandler)
5 (org.eclipse.jetty.websocket.servlet WebSocketServletFactory WebSocketCreator)
6 (java.security.cert X509Certificate)
7 (java.nio ByteBuffer))
8
9 (:require [clojure.tools.logging :as log]
10 [puppetlabs.experimental.websockets.client :refer [WebSocketProtocol]]
11 [schema.core :as schema]
12 [puppetlabs.i18n.core :as i18n]))
13
14 (def WebsocketHandlers
15 {(schema/optional-key :on-connect) IFn
16 (schema/optional-key :on-error) IFn
17 (schema/optional-key :on-close) IFn
18 (schema/optional-key :on-text) IFn
19 (schema/optional-key :on-bytes) IFn})
20
21 (defprotocol WebSocketSend
22 (-send! [x ws] "How to encode content sent to the WebSocket clients"))
23
24 (extend-protocol WebSocketSend
25 (Class/forName "[B")
26 (-send! [ba ws]
27 (-send! (ByteBuffer/wrap ba) ws))
28
29 ByteBuffer
30 (-send! [bb ws]
31 (-> ^WebSocketAdapter ws .getRemote (.sendBytes ^ByteBuffer bb)))
32
33 String
34 (-send! [s ws]
35 (-> ^WebSocketAdapter ws .getRemote (.sendString ^String s))))
36
37 (extend-protocol WebSocketProtocol
38 WebSocketAdapter
39 (send! [this msg]
40 (-send! msg this))
41 (close!
42 ([this]
43 (.. this (getSession) (close)))
44 ([this code reason]
45 (.. this (getSession) (close code reason))))
46 (remote-addr [this]
47 (.. this (getSession) (getRemoteAddress)))
48 (ssl? [this]
49 (.. this (getSession) (getUpgradeRequest) (isSecure)))
50 (peer-certs [this]
51 (.. this (getCerts)))
52 (request-path [this]
53 (.. this (getRequestPath)))
54 (idle-timeout! [this ms]
55 (.. this (getSession) (setIdleTimeout ^long ms)))
56 (connected? [this]
57 (. this (isConnected))))
58
59 (definterface CertGetter
60 (^Object getCerts [])
61 (^String getRequestPath []))
62
63 (defn no-handler
64 [event & args]
65 (log/debug (i18n/trs "No handler defined for websocket event ''{0}'' with args: ''{1}''"
66 event args)))
67
68 (schema/defn ^:always-validate proxy-ws-adapter :- WebSocketAdapter
69 [handlers :- WebsocketHandlers
70 x509certs :- [X509Certificate]
71 requestPath :- String]
72 (let [{:keys [on-connect on-error on-text on-close on-bytes]
73 :or {on-connect (partial no-handler :on-connect)
74 on-error (partial no-handler :on-error)
75 on-text (partial no-handler :on-text)
76 on-close (partial no-handler :on-close)
77 on-bytes (partial no-handler :on-bytes)}} handlers]
78 (proxy [WebSocketAdapter CertGetter] []
79 (onWebSocketConnect [^Session session]
80 (let [^WebSocketAdapter this this]
81 (proxy-super onWebSocketConnect session))
82 (on-connect this))
83 (onWebSocketError [^Throwable e]
84 (let [^WebSocketAdapter this this]
85 (proxy-super onWebSocketError e))
86 (on-error this e))
87 (onWebSocketText [^String message]
88 (let [^WebSocketAdapter this this]
89 (proxy-super onWebSocketText message))
90 (on-text this message))
91 (onWebSocketClose [statusCode ^String reason]
92 (let [^WebSocketAdapter this this]
93 (proxy-super onWebSocketClose statusCode reason))
94 (on-close this statusCode reason))
95 (onWebSocketBinary [^bytes payload offset len]
96 (let [^WebSocketAdapter this this]
97 (proxy-super onWebSocketBinary payload offset len))
98 (on-bytes this payload offset len))
99 (getCerts [] x509certs)
100 (getRequestPath [] requestPath))))
101
102 (schema/defn ^:always-validate proxy-ws-creator :- WebSocketCreator
103 [handlers :- WebsocketHandlers]
104 (reify WebSocketCreator
105 (createWebSocket [this req _]
106 (let [x509certs (vec (.. req (getCertificates)))
107 requestPath (.. req (getRequestPath))]
108 (proxy-ws-adapter handlers x509certs requestPath)))))
109
110 (schema/defn ^:always-validate websocket-handler :- WebSocketHandler
111 "Returns a Jetty WebSocketHandler implementation for the given set of Websocket handlers"
112 [handlers :- WebsocketHandlers]
113 (proxy [WebSocketHandler] []
114 (configure [^WebSocketServletFactory factory]
115 (.setCreator factory (proxy-ws-creator handlers)))
116 (handle [^String target, ^Request request req res]
117 (let [wsf (proxy-super getWebSocketFactory)]
118 (if (.isUpgradeRequest wsf req res)
119 (if (.acceptWebSocket wsf req res)
120 (.setHandled request true)
121 (when (.isCommitted res)
122 (.setHandled request true)))
123 (proxy-super handle target request req res))))))
0 (ns puppetlabs.trapperkeeper.services.webserver.jetty9-config
1 (:import [java.security KeyStore]
2 (java.io FileInputStream)
3 (org.eclipse.jetty.server.handler RequestLogHandler)
4 (ch.qos.logback.access.jetty RequestLogImpl)
5 (org.eclipse.jetty.server Server)
6 (org.codehaus.janino ScriptEvaluator)
7 (org.codehaus.commons.compiler CompileException)
8 (java.lang.reflect InvocationTargetException))
9 (:require [clojure.tools.logging :as log]
10 [clojure.string :as str]
11 [me.raynes.fs :as fs]
12 [schema.core :as schema]
13 [puppetlabs.ssl-utils.core :as ssl]
14 [puppetlabs.kitchensink.core :refer [missing? num-cpus uuid parse-bool]]
15 [puppetlabs.i18n.core :as i18n]))
16
17 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
18 ;;; Constants / Defaults
19 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
20 ;;;
21 ;;; NOTE: We are making a decisive move away from overriding Jetty's
22 ;;; implicit default values for settings when downstream TK apps do not
23 ;;; explicitly provide values for them. Please see the comments/tests in
24 ;;; `jetty9_default_config_test.clj` for full details.
25 ;;;
26 ;;; Below we are making a handful of deliberate exceptions to this rule,
27 ;;; but please do not perpetuate this pattern without a compelling reason to
28 ;;; do so.
29
30
31 ;;;
32 ;;; Host/port settings
33 ;;;
34 ;;; These are really common and fairly benign, and removing them would probably
35 ;;; only serve to make the bare configuration more onerous.
36 ;;;
37 (def default-http-port 8080)
38 (def default-https-port 8081)
39 (def default-host "localhost")
40
41 ;;;
42 ;;; Security-related settings
43 ;;;
44 ;;; After some discussion, we decided that it was probably still appropriate to
45 ;;; override Jetty's defaults for these security-related settings. In the event
46 ;;; that a vulnerability like "POODLE" is announced (where we needed to remove
47 ;;; the SSLv3 protocol from the list of allowed protocols), we would need to do
48 ;;; a release of tk-j9 to address it no matter what. The choices would then be
49 ;;; to update our own defaults for security-related settings, or, if we're not
50 ;;; imposing our own defaults, to try to upgrade to a new version of Jetty where
51 ;;; their implicit defaults reflect the security issue. The latter is far more
52 ;;; risky for our downstream apps, thus it was decided that it makes sense to
53 ;;; keep these overrides.
54 ;;;
55 ;;; Also note that w/rt the default list of acceptable ciphers, we're deliberately
56 ;;; excluding all diffie-helman ciphers due to some old JDK bugs:
57 ;;;
58 ;;; http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8014618
59 ;;; https://github.com/puppetlabs/puppetdb/commit/03e020dc85b83d6c83c9992ca6bd14f57e8fc91a
60 ;;;
61 ;;; These have been fixed in recent versions of the JDK, and it would be nice
62 ;;; to be able to add the DH ciphers back in at some point, but we can't do that
63 ;;; until we're certain that our minimum supported JDK versions for all of our
64 ;;; supported distros will contain the relevant fixes.
65 ;;;
66 (def acceptable-ciphers
67 ["TLS_RSA_WITH_AES_256_CBC_SHA256"
68 "TLS_RSA_WITH_AES_256_CBC_SHA"
69 "TLS_RSA_WITH_AES_128_CBC_SHA256"
70 "TLS_RSA_WITH_AES_128_CBC_SHA"
71 "SSL_RSA_WITH_RC4_128_SHA"
72 "SSL_RSA_WITH_3DES_EDE_CBC_SHA"
73 "SSL_RSA_WITH_RC4_128_MD5"])
74 (def default-protocols ["TLSv1" "TLSv1.1" "TLSv1.2"])
75 (def default-client-auth :need)
76
77 ;;;
78 ;;; JMX
79 ;;;
80 ;;; The JMX metrics seem valuable enough, and inexpensive enough, to warrant
81 ;;; leaving them on by default.
82 (def default-jmx-enable "true")
83
84 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
85 ;;; Schemas
86
87 (def StaticContent
88 {:resource schema/Str
89 :path schema/Str
90 (schema/optional-key :follow-links) schema/Bool})
91
92 (def WebserverRawConfig
93 {(schema/optional-key :port) schema/Int
94 (schema/optional-key :host) schema/Str
95 (schema/optional-key :acceptor-threads) schema/Int
96 (schema/optional-key :selector-threads) schema/Int
97 (schema/optional-key :max-threads) schema/Int
98 (schema/optional-key :queue-max-size) schema/Int
99 (schema/optional-key :request-header-max-size) schema/Int
100 (schema/optional-key :request-body-max-size) schema/Int
101 (schema/optional-key :so-linger-seconds) schema/Int
102 (schema/optional-key :idle-timeout-milliseconds) schema/Int
103 (schema/optional-key :ssl-port) schema/Int
104 (schema/optional-key :ssl-host) schema/Str
105 (schema/optional-key :ssl-key) schema/Str
106 (schema/optional-key :ssl-cert) schema/Str
107 (schema/optional-key :ssl-cert-chain) schema/Str
108 (schema/optional-key :ssl-ca-cert) schema/Str
109 (schema/optional-key :ssl-acceptor-threads) schema/Int
110 (schema/optional-key :ssl-selector-threads) schema/Int
111 (schema/optional-key :keystore) schema/Str
112 (schema/optional-key :truststore) schema/Str
113 (schema/optional-key :key-password) schema/Str
114 (schema/optional-key :trust-password) schema/Str
115 (schema/optional-key :cipher-suites) (schema/either schema/Str [schema/Str])
116 (schema/optional-key :ssl-protocols) (schema/either schema/Str [schema/Str])
117 (schema/optional-key :client-auth) schema/Str
118 (schema/optional-key :ssl-crl-path) schema/Str
119 (schema/optional-key :jmx-enable) schema/Str
120 (schema/optional-key :default-server) schema/Bool
121 (schema/optional-key :static-content) [StaticContent]
122 (schema/optional-key :gzip-enable) schema/Bool
123 (schema/optional-key :access-log-config) schema/Str
124 (schema/optional-key :shutdown-timeout-seconds) schema/Int
125 (schema/optional-key :post-config-script) schema/Str})
126
127 (def MultiWebserverRawConfigUnvalidated
128 {schema/Keyword WebserverRawConfig})
129
130 (defn one-default?
131 [config]
132 (->> config
133 vals
134 (filter :default-server)
135 count
136 (>= 1)))
137
138 (defn map-of-maps? [x]
139 (and (map? x)
140 (every? map? (vals x))))
141
142 (def MultiWebserverRawConfig
143 (schema/both MultiWebserverRawConfigUnvalidated (schema/pred one-default? 'one-default?)))
144
145 (def WebserverServiceRawConfig
146 (schema/conditional
147 map-of-maps? MultiWebserverRawConfig
148 :else WebserverRawConfig))
149
150 (def WebserverSslPemConfig
151 {:ssl-key schema/Str
152 :ssl-cert schema/Str
153 (schema/optional-key :ssl-cert-chain) schema/Str
154 :ssl-ca-cert schema/Str})
155
156 (def WebserverSslKeystoreConfig
157 {:keystore KeyStore
158 :key-password schema/Str
159 :truststore KeyStore
160 (schema/optional-key :trust-password) schema/Str})
161
162 (def WebserverSslClientAuth
163 (schema/enum :need :want :none))
164
165 (def WebserverConnectorCommon
166 {:request-header-max-size (schema/maybe schema/Int)
167 :so-linger-milliseconds (schema/maybe schema/Int)
168 :idle-timeout-milliseconds (schema/maybe schema/Int)})
169
170 (def WebserverConnector
171 (merge WebserverConnectorCommon
172 {:host schema/Str
173 :port schema/Int
174 :acceptor-threads (schema/maybe schema/Int)
175 :selector-threads (schema/maybe schema/Int)}))
176
177 (def WebserverSslContextFactory
178 {:keystore-config WebserverSslKeystoreConfig
179 :client-auth WebserverSslClientAuth
180 (schema/optional-key :ssl-crl-path) (schema/maybe schema/Str)
181 :cipher-suites [schema/Str]
182 :protocols (schema/maybe [schema/Str])})
183
184 (def WebserverSslConnector
185 (merge
186 WebserverConnector
187 WebserverSslContextFactory))
188
189 (def HasConnector
190 (schema/either
191 (schema/pred #(contains? % :http) 'has-http-connector?)
192 (schema/pred #(contains? % :https) 'has-https-connector?)))
193
194 (def WebserverConfig
195 (schema/both
196 HasConnector
197 {(schema/optional-key :http) WebserverConnector
198 (schema/optional-key :https) WebserverSslConnector
199 :max-threads (schema/maybe schema/Int)
200 :queue-max-size (schema/maybe schema/Int)
201 :jmx-enable schema/Bool}))
202
203 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
204 ;;; Conversion functions (raw config -> schema)
205
206 (schema/defn ^:always-validate
207 maybe-get-pem-config! :- (schema/maybe WebserverSslPemConfig)
208 [config :- WebserverRawConfig]
209 (let [pem-required-keys [:ssl-key :ssl-cert :ssl-ca-cert]
210 pem-config (select-keys config pem-required-keys)]
211 (condp = (count pem-config)
212 3 (if-let [ssl-cert-chain (:ssl-cert-chain config)]
213 (assoc pem-config :ssl-cert-chain ssl-cert-chain)
214 pem-config)
215 0 nil
216 (throw (IllegalArgumentException.
217 (i18n/trs "Found SSL config options: {0}; If configuring SSL from PEM files, you must provide all of the following options: {1}"
218 (keys pem-config) pem-required-keys))))))
219
220 (schema/defn ^:always-validate
221 get-x509s-from-ssl-cert-pem :- (schema/pred ssl/certificate-list?)
222 [ssl-cert :- schema/Str
223 ssl-cert-chain :- (schema/maybe schema/Str)]
224 (if-not (fs/readable? ssl-cert)
225 (throw (IllegalArgumentException. (i18n/trs "Unable to open ''ssl-cert'' file: {0}" ssl-cert))))
226 (let [certs (ssl/pem->certs ssl-cert)]
227 (if (= 0 (count certs))
228 (throw (Exception. (i18n/trs "No certs found in ''ssl-cert'' file: {0}" ssl-cert))))
229 (if ssl-cert-chain
230 [(first certs)]
231 certs)))
232
233 (schema/defn ^:always-validate
234 get-x509s-from-ssl-cert-chain-pem :- (schema/pred ssl/certificate-list?)
235 [ssl-cert-chain :- (schema/maybe schema/Str)]
236 (if ssl-cert-chain
237 (do
238 (if-not (fs/readable? ssl-cert-chain)
239 (throw (IllegalArgumentException.
240 (i18n/trs "Unable to open ''ssl-cert-chain'' file: {0}"
241 ssl-cert-chain))))
242 (ssl/pem->certs ssl-cert-chain))
243 []))
244
245 (schema/defn ^:always-validate
246 construct-ssl-x509-cert-chain :- (schema/pred ssl/certificate-list?)
247 [ssl-cert :- schema/Str
248 ssl-cert-chain :- (schema/maybe schema/Str)]
249 (let [ssl-cert-x509s (get-x509s-from-ssl-cert-pem ssl-cert ssl-cert-chain)
250 ssl-cert-chain-x509s (get-x509s-from-ssl-cert-chain-pem ssl-cert-chain)]
251 (into [] (concat ssl-cert-x509s ssl-cert-chain-x509s))))
252
253 (schema/defn ^:always-validate
254 pem-ssl-config->keystore-ssl-config :- WebserverSslKeystoreConfig
255 [{:keys [ssl-ca-cert ssl-key ssl-cert ssl-cert-chain]} :- WebserverSslPemConfig]
256 (let [key-password (uuid)
257 ssl-x509-chain (construct-ssl-x509-cert-chain ssl-cert
258 ssl-cert-chain)]
259 {:truststore (-> (ssl/keystore)
260 (ssl/assoc-certs-from-file!
261 "CA Certificate" ssl-ca-cert))
262 :key-password key-password
263 :keystore (-> (ssl/keystore)
264 (ssl/assoc-private-key!
265 "Private Key"
266 (ssl/pem->private-key ssl-key)
267 key-password
268 ssl-x509-chain))}))
269
270 (schema/defn ^:always-validate
271 warn-if-keystore-ssl-configs-found!
272 [config :- WebserverRawConfig]
273 (let [keystore-ssl-config-keys [:keystore :truststore :key-password :trust-password]
274 keystore-ssl-config (select-keys config keystore-ssl-config-keys)]
275 (when (pos? (count keystore-ssl-config))
276 (log/warn (i18n/trs "Found settings for both keystore-based and PEM-based SSL; using PEM-based settings, ignoring {0}"
277 (keys keystore-ssl-config))))))
278
279 (schema/defn ^:always-validate
280 get-jks-keystore-config! :- WebserverSslKeystoreConfig
281 [{:keys [truststore keystore key-password trust-password]}
282 :- WebserverRawConfig]
283 (when (some nil? [truststore keystore key-password trust-password])
284 (throw (IllegalArgumentException.
285 (i18n/trs "Missing some SSL configuration; must provide either :ssl-cert, :ssl-key, and :ssl-ca-cert, OR :truststore, :trust-password, :keystore, and :key-password."))))
286 {:keystore (doto (ssl/keystore)
287 (.load (FileInputStream. keystore)
288 (.toCharArray key-password)))
289 :truststore (doto (ssl/keystore)
290 (.load (FileInputStream. truststore)
291 (.toCharArray trust-password)))
292 :key-password key-password
293 :trust-password trust-password})
294
295 (schema/defn ^:always-validate
296 get-keystore-config! :- WebserverSslKeystoreConfig
297 [config :- WebserverRawConfig]
298 (if-let [pem-config (maybe-get-pem-config! config)]
299 (do
300 (warn-if-keystore-ssl-configs-found! config)
301 (pem-ssl-config->keystore-ssl-config pem-config))
302 (get-jks-keystore-config! config)))
303
304 (schema/defn ^:always-validate
305 get-client-auth! :- WebserverSslClientAuth
306 [config :- WebserverRawConfig]
307 (let [client-auth (:client-auth config)]
308 (cond
309 (nil? client-auth) default-client-auth
310 (contains? #{"need" "want" "none"} client-auth) (keyword client-auth)
311 :else (throw
312 (IllegalArgumentException.
313 (i18n/trs "Unexpected value found for client auth config option: {0}. Expected need, want, or none."
314 client-auth))))))
315
316 (schema/defn ^:always-validate
317 get-ssl-crl-path! :- (schema/maybe schema/Str)
318 [config :- WebserverRawConfig]
319 (if-let [ssl-crl-path (:ssl-crl-path config)]
320 (if (fs/readable? ssl-crl-path)
321 ssl-crl-path
322 (throw (IllegalArgumentException.
323 (i18n/trs "Non-readable path specified for ssl-crl-path option: {0}"
324 ssl-crl-path))))))
325
326 (schema/defn get-or-parse-sequential-config-value :- [schema/Str]
327 "Some config values can be entered as either a vector of strings or
328 a single comma-separated string. Get the value for the given config
329 key, parsing it into a seq if it's a string, or returning a default
330 if it's not provided."
331 [config :- WebserverRawConfig
332 key :- schema/Keyword
333 default :- [schema/Str]]
334 (let [value (key config)]
335 (cond
336 (string? value) (map str/trim (str/split value #","))
337 value value
338 :else default)))
339
340 (defn get-cipher-suites-config [config]
341 (get-or-parse-sequential-config-value config :cipher-suites acceptable-ciphers))
342
343 (defn get-ssl-protocols-config [config]
344 (get-or-parse-sequential-config-value config :ssl-protocols default-protocols))
345
346 (schema/defn ^:always-validate
347 contains-keys? :- schema/Bool
348 [config :- WebserverRawConfig
349 keys :- #{schema/Keyword}]
350 (boolean (some #(contains? config %) keys)))
351
352 (defn contains-http-connector? [config]
353 (contains-keys? config #{:port :host}))
354
355 (schema/defn ^:always-validate
356 so-linger-in-milliseconds :- (schema/maybe schema/Int)
357 [config :- WebserverRawConfig]
358 (when-let [linger-from-config (:so-linger-seconds config)]
359 (* 1000 linger-from-config)))
360
361 (schema/defn ^:always-validate
362 common-connector-config :- WebserverConnectorCommon
363 [config :- WebserverRawConfig]
364 {:request-header-max-size (:request-header-max-size config)
365 :so-linger-milliseconds (so-linger-in-milliseconds config)
366 :idle-timeout-milliseconds (:idle-timeout-milliseconds config)})
367
368 (schema/defn ^:always-validate
369 maybe-get-http-connector :- (schema/maybe WebserverConnector)
370 [config :- WebserverRawConfig]
371 (if (contains-http-connector? config)
372 (merge (common-connector-config config)
373 {:host (or (:host config) default-host)
374 :port (or (:port config) default-http-port)
375 :acceptor-threads (:acceptor-threads config)
376 :selector-threads (:selector-threads config)})))
377
378 (schema/defn ^:always-validate
379 contains-https-connector? :- schema/Bool
380 [config :- WebserverRawConfig]
381 (contains-keys? config #{:ssl-port :ssl-host}))
382
383 (schema/defn ^:always-validate
384 maybe-get-https-connector :- (schema/maybe WebserverSslConnector)
385 [config :- WebserverRawConfig]
386 (if (contains-https-connector? config)
387 (merge (common-connector-config config)
388 {:host (or (:ssl-host config) default-host)
389 :port (or (:ssl-port config) default-https-port)
390 :acceptor-threads (:ssl-acceptor-threads config)
391 :selector-threads (:ssl-selector-threads config)
392 :keystore-config (get-keystore-config! config)
393 :cipher-suites (get-cipher-suites-config config)
394 :protocols (get-ssl-protocols-config config)
395 :client-auth (get-client-auth! config)
396 :ssl-crl-path (get-ssl-crl-path! config)})))
397
398 (schema/defn ^:always-validate
399 maybe-add-http-connector :- {(schema/optional-key :http) WebserverConnector
400 schema/Keyword schema/Any}
401 [acc config :- WebserverRawConfig]
402 (if-let [http-connector (maybe-get-http-connector config)]
403 (assoc acc :http http-connector)
404 acc))
405
406 (schema/defn ^:always-validate
407 maybe-add-https-connector :- {(schema/optional-key :https) WebserverSslConnector
408 schema/Keyword schema/Any}
409 [acc config :- WebserverRawConfig]
410 (if-let [https-connector (maybe-get-https-connector config)]
411 (assoc acc :https https-connector)
412 acc))
413
414 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
415 ;;; Private helper functions
416
417 (defn validate-config
418 [config]
419 (when-not (one-default? config)
420 (throw (IllegalArgumentException.
421 (i18n/trs "Error: More than one default server specified in configuration")))))
422
423 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
424 ;;; Public
425
426 (schema/defn ^:always-validate
427 process-config :- WebserverConfig
428 [config :- WebserverRawConfig]
429 (let [result (-> {}
430 (maybe-add-http-connector config)
431 (maybe-add-https-connector config)
432 (assoc :max-threads (:max-threads config))
433 (assoc :queue-max-size (:queue-max-size config))
434 (assoc :jmx-enable (parse-bool
435 (get config
436 :jmx-enable default-jmx-enable))))]
437 (when-not (some #(contains? result %) [:http :https])
438 (throw (IllegalArgumentException.
439 (i18n/trs "Either host, port, ssl-host, or ssl-port must be specified on the config in order for the server to be started"))))
440 result))
441
442 (schema/defn ^:always-validate
443 init-log-handler :- RequestLogHandler
444 [config :- WebserverRawConfig]
445 (let [handler (RequestLogHandler.)
446 logger (RequestLogImpl.)]
447 (.setFileName logger (:access-log-config config))
448 (.setQuiet logger true)
449 (.setRequestLog handler logger)
450 handler))
451
452 (defn maybe-init-log-handler
453 [config]
454 (if (:access-log-config config)
455 (init-log-handler config)))
456
457 (schema/defn ^:always-validate
458 execute-post-config-script!
459 [s :- Server
460 script :- schema/Str]
461 (log/warn (i18n/trs "The ''post-config-script'' setting is for advanced use cases only, and may be subject to minor changes when the application is upgraded."))
462 (let [script-err-msg (i18n/trs "Invalid script string in webserver ''post-config-script'' configuration")]
463 (try
464 (let [evaluator (doto (ScriptEvaluator.)
465 (.setParameters (into-array String ["server"])
466 (into-array Class [Server]))
467 (.cook script))]
468 (.evaluate evaluator (into-array Object [s])))
469 (catch CompileException ex
470 (throw (IllegalArgumentException. script-err-msg ex)))
471 (catch InvocationTargetException ex
472 (throw (IllegalArgumentException. script-err-msg ex))))))
0 (ns puppetlabs.trapperkeeper.services.webserver.jetty9-core
1 (:import (org.eclipse.jetty.server Handler Server Request ServerConnector
2 HttpConfiguration HttpConnectionFactory
3 ConnectionFactory AbstractConnectionFactory)
4 (org.eclipse.jetty.server.handler AbstractHandler ContextHandler HandlerCollection
5 ContextHandlerCollection AllowSymLinkAliasChecker StatisticsHandler HandlerWrapper)
6 (org.eclipse.jetty.util.resource Resource)
7 (org.eclipse.jetty.util.thread QueuedThreadPool)
8 (org.eclipse.jetty.util.ssl SslContextFactory)
9 (javax.servlet.http HttpServletResponse)
10 (java.util.concurrent TimeoutException)
11 (org.eclipse.jetty.servlets.gzip GzipHandler)
12 (org.eclipse.jetty.servlet ServletContextHandler ServletHolder DefaultServlet)
13 (org.eclipse.jetty.webapp WebAppContext)
14 (java.util HashSet)
15 (org.eclipse.jetty.http MimeTypes HttpHeader HttpHeaderValue)
16 (javax.servlet Servlet ServletContextListener)
17 (org.eclipse.jetty.proxy ProxyServlet)
18 (java.net URI)
19 (java.security Security)
20 (org.eclipse.jetty.client HttpClient)
21 (clojure.lang Atom)
22 (java.lang.management ManagementFactory)
23 (org.eclipse.jetty.jmx MBeanContainer)
24 (org.eclipse.jetty.util URIUtil BlockingArrayQueue)
25 (java.io IOException))
26
27 (:require [ring.util.servlet :as servlet]
28 [ring.util.codec :as codec]
29 [clojure.string :as str]
30 [clojure.tools.logging :as log]
31 [puppetlabs.trapperkeeper.services.webserver.jetty9-config :as config]
32 [puppetlabs.trapperkeeper.services.webserver.experimental.jetty9-websockets :as websockets]
33 [puppetlabs.trapperkeeper.services.webserver.normalized-uri-helpers
34 :as normalized-uri-helpers]
35 [schema.core :as schema]
36 [puppetlabs.i18n.core :as i18n]))
37
38 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
39 ;;; JDK SecurityProvider Hack
40
41 ;; Work around an issue with OpenJDK's PKCS11 implementation preventing TLSv1
42 ;; connections from working correctly
43 ;;
44 ;; http://stackoverflow.com/questions/9586162/openjdk-and-php-ssl-connection-fails
45 ;; https://bugs.launchpad.net/ubuntu/+source/openjdk-6/+bug/948875
46 (if (re-find #"OpenJDK" (System/getProperty "java.vm.name"))
47 (try
48 (let [klass (Class/forName "sun.security.pkcs11.SunPKCS11")
49 blacklist (filter #(instance? klass %) (Security/getProviders))]
50 (doseq [provider blacklist]
51 (log/info (i18n/trs "Removing buggy security provider {0}" provider))
52 (Security/removeProvider (.getName provider))))
53 (catch ClassNotFoundException e)
54 (catch Throwable e
55 (log/error e (i18n/trs "Could not remove security providers; HTTPS may not work!")))))
56
57 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
58 ;;; Schemas
59
60 (def ProxyTarget
61 {:host schema/Str
62 :path schema/Str
63 :port schema/Int})
64
65 (def CommonOptions
66 {(schema/optional-key :server-id) schema/Keyword
67 (schema/optional-key :redirect-if-no-trailing-slash) schema/Bool
68 (schema/optional-key :normalize-request-uri) schema/Bool})
69
70 (def ContextHandlerOptions
71 (assoc CommonOptions (schema/optional-key :context-listeners) [ServletContextListener]
72 (schema/optional-key :follow-links) schema/Bool))
73
74 (def ServletHandlerOptions
75 (assoc CommonOptions (schema/optional-key :servlet-init-params) {schema/Str schema/Str}))
76
77 (def ProxySslConfig
78 (merge config/WebserverSslPemConfig
79 {(schema/optional-key :cipher-suites) [schema/Str]
80 (schema/optional-key :protocols) (schema/maybe [schema/Str])}))
81
82 (def ProxyOptions
83 (assoc CommonOptions
84 (schema/optional-key :scheme) (schema/enum :orig :http :https
85 "orig" "http" "https")
86 (schema/optional-key :ssl-config) (schema/conditional
87 keyword? (schema/eq :use-server-config)
88 map? ProxySslConfig)
89 (schema/optional-key :rewrite-uri-callback-fn) (schema/pred ifn?)
90 (schema/optional-key :callback-fn) (schema/pred ifn?)
91 (schema/optional-key :failure-callback-fn) (schema/pred ifn?)
92 (schema/optional-key :request-buffer-size) schema/Int
93 (schema/optional-key :follow-redirects) schema/Bool
94 (schema/optional-key :idle-timeout) (schema/both schema/Int
95 (schema/pred pos?))))
96
97 (def ContextEndpoint
98 {:type (schema/eq :context)
99 :base-path schema/Str
100 (schema/optional-key :context-listeners) (schema/maybe [ServletContextListener])})
101
102 (def RingEndpoint
103 {:type (schema/eq :ring)})
104
105 (def WebsocketEndpoint
106 {:type (schema/eq :websocket)})
107
108 (def ServletEndpoint
109 {:type (schema/eq :servlet)
110 :servlet java.lang.Class})
111
112 (def WarEndpoint
113 {:type (schema/eq :war)
114 :war-path schema/Str})
115
116 (def ProxyEndpoint
117 {:type (schema/eq :proxy)
118 :target-host schema/Str
119 :target-port schema/Int
120 :target-path schema/Str})
121
122 (def Endpoint
123 (schema/conditional
124 #(-> % :type (= :context)) ContextEndpoint
125 #(-> % :type (= :ring)) RingEndpoint
126 #(-> % :type (= :websocket)) WebsocketEndpoint
127 #(-> % :type (= :servlet)) ServletEndpoint
128 #(-> % :type (= :war)) WarEndpoint
129 #(-> % :type (= :proxy)) ProxyEndpoint))
130
131 (def RegisteredEndpoints
132 {schema/Str [Endpoint]})
133
134 (def ServerContextState
135 {:mbean-container (schema/maybe MBeanContainer)
136 :overrides-read-by-webserver schema/Bool
137 :overrides (schema/maybe {schema/Keyword schema/Any})
138 :endpoints RegisteredEndpoints
139 :ssl-context-factory (schema/maybe SslContextFactory)})
140
141 (def ServerContext
142 {:state (schema/atom ServerContextState)
143 :handlers ContextHandlerCollection
144 :server (schema/maybe Server)})
145
146 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
147 ;;; Utility Functions
148
149 (defn- remove-leading-slash
150 [s]
151 (str/replace s #"^\/" ""))
152
153 (defn- with-leading-slash
154 [s]
155 (if (.startsWith s "/")
156 s
157 (str "/" s)))
158
159 (schema/defn ^:always-validate started? :- Boolean
160 "A predicate that indicates whether or not the webserver-context contains a Jetty
161 Server object."
162 [webserver-context :- ServerContext]
163 (instance? Server (:server webserver-context)))
164
165 (schema/defn ^:always-validate
166 merge-webserver-overrides-with-options :- config/WebserverRawConfig
167 "Merge any overrides made to the webserver config settings with the supplied
168 options."
169 [webserver-context :- ServerContext
170 options :- config/WebserverRawConfig]
171 (let [overrides (:overrides (swap! (:state webserver-context)
172 assoc
173 :overrides-read-by-webserver
174 true))]
175 (doseq [key (keys overrides)]
176 (log/info (i18n/trs "webserver config overridden for key ''{0}''" (name key))))
177 (merge options overrides)))
178
179 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
180 ;;; SSL Context Functions
181
182 (schema/defn ^:always-validate
183 ssl-context-factory :- SslContextFactory
184 "Creates a new SslContextFactory instance from a map of SSL config options."
185 [{:keys [keystore-config client-auth ssl-crl-path cipher-suites protocols]}
186 :- config/WebserverSslContextFactory]
187 (if (some #(= "sslv3" %) (map str/lower-case protocols))
188 (log/warn (i18n/trs "`ssl-protocols` contains SSLv3, a protocol with known vulnerabilities; we recommend removing it from the `ssl-protocols` list")))
189
190 (let [context (doto (SslContextFactory.)
191 (.setKeyStore (:keystore keystore-config))
192 (.setKeyStorePassword (:key-password keystore-config))
193 (.setTrustStore (:truststore keystore-config))
194 ;; Need to clear out the default cipher suite exclude list so
195 ;; that Jetty doesn't potentially remove one or more ciphers
196 ;; that we want to be included.
197 (.setExcludeCipherSuites (into-array String []))
198 (.setIncludeCipherSuites (into-array String cipher-suites))
199 ;; Need to clear out the default protocols exclude list so
200 ;; that Jetty doesn't potentially remove one or more protocols
201 ;; that we want to be included.
202 (.setExcludeProtocols (into-array String []))
203 (.setIncludeProtocols (into-array String protocols)))]
204 (if (:trust-password keystore-config)
205 (.setTrustStorePassword context (:trust-password keystore-config)))
206 (case client-auth
207 :need (.setNeedClientAuth context true)
208 :want (.setWantClientAuth context true)
209 nil)
210 (when ssl-crl-path
211 (.setCrlPath context ssl-crl-path)
212 ; .setValidatePeerCerts needs to be called with a value of 'true' in
213 ; order to force Jetty to actually use the CRL when validating client
214 ; certificates for a connection.
215 (.setValidatePeerCerts context true))
216 context))
217
218 (schema/defn ^:always-validate
219 get-proxy-client-context-factory :- SslContextFactory
220 [ssl-config :- ProxySslConfig]
221 (ssl-context-factory {:keystore-config
222 (config/pem-ssl-config->keystore-ssl-config
223 ssl-config)
224 :client-auth :none
225 :cipher-suites (or (:cipher-suites ssl-config) config/acceptable-ciphers)
226 :protocols (or (:protocols ssl-config) config/default-protocols)}))
227
228 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
229 ;;; Jetty Server / Connector Functions
230
231 (defn- http-configuration
232 [request-header-size]
233 (let [http-config (doto (HttpConfiguration.)
234 (.setSendDateHeader true))]
235 (if request-header-size
236 (.setRequestHeaderSize http-config request-header-size))
237 http-config))
238
239 (defn- connection-factories
240 [request-header-size ssl-ctxt-factory]
241 (let [http-config (http-configuration request-header-size)
242 factories (into-array ConnectionFactory
243 [(HttpConnectionFactory. http-config)])]
244 (if ssl-ctxt-factory
245 (AbstractConnectionFactory/getFactories
246 ssl-ctxt-factory factories)
247 factories)))
248
249 (defn- thread-count
250 [setting]
251 (if setting setting -1))
252
253 (schema/defn ^:always-validate
254 connector* :- ServerConnector
255 [server :- Server
256 config :- (merge config/WebserverConnector
257 {schema/Keyword schema/Any})
258 ssl-ctxt-factory :- (schema/maybe SslContextFactory)]
259 (let [request-size (:request-header-max-size config)
260 connector (doto (ServerConnector.
261 server
262 nil nil nil
263 (thread-count (:acceptor-threads config))
264 (thread-count (:selector-threads config))
265 (connection-factories request-size ssl-ctxt-factory))
266 (.setPort (:port config))
267 (.setHost (:host config)))]
268 (when-let [linger-millis (:so-linger-milliseconds config)]
269 (.setSoLingerTime connector linger-millis))
270 (when-let [idle-timeout (:idle-timeout-milliseconds config)]
271 (.setIdleTimeout connector idle-timeout))
272 connector))
273
274 (schema/defn ^:always-validate
275 ssl-connector :- ServerConnector
276 "Creates a ssl ServerConnector instance."
277 [server :- Server
278 ssl-ctxt-factory :- SslContextFactory
279 config :- config/WebserverSslConnector]
280 (connector* server config ssl-ctxt-factory))
281
282 (schema/defn ^:always-validate
283 plaintext-connector :- ServerConnector
284 [server :- Server
285 config :- config/WebserverConnector]
286 (connector* server config nil))
287
288 (schema/defn ^:always-validate
289 queue-thread-pool :- (schema/maybe QueuedThreadPool)
290 [max-threads :- (schema/maybe schema/Int)
291 queue-max-size :- (schema/maybe schema/Int)]
292 (if (or max-threads queue-max-size)
293 (let [thread-pool (if max-threads
294 (QueuedThreadPool. max-threads)
295 (QueuedThreadPool.))]
296 (if queue-max-size
297 ;; The code below is definitely not ideal, but there isn't a way to set
298 ;; the maximum capacity of the QueuedThreadPool's BlockingArrayQueue
299 ;; after construction. We're trying to avoid hard-coding our own
300 ;; defaults for other settings that we want Jetty to control, e.g., the
301 ;; initial capacity of the queue and minimum number of threads. By
302 ;; reconstructing the QueuedThreadPool here, we can use Jetty's defaults
303 ;; for settings unrelated to `queue-max-size`.
304 ;;
305 ;; QueuedThreadPool and BlockingArrayQueue construction isn't too
306 ;; expensive. It mostly involves some initial memory allocations. The
307 ;; more expensive work - where threads are actually started and the
308 ;; queue expands to fulfill new requests - would only happen after the
309 ;; QueuedThreadPool were started. That won't happen for the
310 ;; `thread-pool` instance from above, which just gets thrown away as
311 ;; this function falls out of scope without ever having been started.
312 ;; Also, this function would likely only be called once per server
313 ;; startup where a `queue-max-size` were configured.
314 ;;
315 ;; The QueuedThreadPool constructor sets the `queue-capacity` and
316 ;; `queue-grow-by` based on the minimum number of threads available
317 ;; in the pool. See https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java#L92-L96.
318 ;; That algorithm is essentially duplicated here, with the only
319 ;; difference being that if `queue-max-size` is smaller than the
320 ;; minimum number of threads, the `queue-capacity` and `queue-grow-by`
321 ;; are reduced to the `queue-max-size` in order to avoid the
322 ;; BlockingArrayQueue constructor throwing an IllegalArgumentException.
323 (let [min-threads (.getMinThreads thread-pool)
324 queue-capacity (min queue-max-size min-threads)
325 queue-grow-by (min queue-max-size min-threads)]
326 (QueuedThreadPool. (.getMaxThreads thread-pool)
327 min-threads
328 (.getIdleTimeout thread-pool)
329 (BlockingArrayQueue.
330 queue-capacity
331 queue-grow-by
332 queue-max-size)))
333 thread-pool))))
334
335 (schema/defn ^:always-validate
336 create-server :- Server
337 "Construct a Jetty Server instance."
338 [webserver-context :- ServerContext
339 config :- config/WebserverConfig]
340 (let [server (if-let [pool (queue-thread-pool (:max-threads config)
341 (:queue-max-size config))]
342 (Server. pool)
343 (Server.))]
344 (when (:jmx-enable config)
345 (let [mb-container (MBeanContainer. (ManagementFactory/getPlatformMBeanServer))]
346 (doto server
347 (.addEventListener mb-container)
348 (.addBean mb-container))
349 (swap! (:state webserver-context) assoc :mbean-container mb-container)))
350 (when (:http config)
351 (let [connector (plaintext-connector server (:http config))]
352 (.addConnector server connector)))
353 (when-let [https (:https config)]
354 (let [ssl-ctxt-factory (ssl-context-factory
355 {:keystore-config (:keystore-config https)
356 :client-auth (:client-auth https)
357 :ssl-crl-path (:ssl-crl-path https)
358 :cipher-suites (:cipher-suites https)
359 :protocols (:protocols https)})
360 connector (ssl-connector server ssl-ctxt-factory https)]
361
362 (.addConnector server connector)
363 (swap! (:state webserver-context) assoc :ssl-context-factory ssl-ctxt-factory)))
364 server))
365
366 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
367 ;;; GZip Functions
368
369 ;; TODO: make all of this gzip-mime-type stuff configurable
370 (defn- gzip-excluded-mime-types
371 "Build up a list of mime types that should not be candidates for
372 gzip compression in responses."
373 []
374 (->
375 ;; This code is ported from Jetty 9.0.5's GzipFilter class. In
376 ;; Jetty 7, this behavior was the default for GzipHandler as well
377 ;; as GzipFilter, but in Jetty 9.0.5 the GzipHandler no longer
378 ;; includes this, so we need to do it by hand.
379 (filter #(or (.startsWith % "image/")
380 (.startsWith % "audio/")
381 (.startsWith % "video/"))
382 (MimeTypes/getKnownMimeTypes))
383 (conj "application/compress" "application/zip" "application/gzip" "text/event-stream")
384 (HashSet.)))
385
386 (defn- gzip-handler
387 "Given a handler, wrap it with a GzipHandler that will compress the response
388 when appropriate."
389 [handler]
390 (doto (GzipHandler.)
391 (.setHandler handler)
392 (.setMimeTypes (gzip-excluded-mime-types))
393 (.setExcludeMimeTypes true)))
394
395 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
396 ;;; Handler Helper Functions
397
398 (schema/defn ^:always-validate
399 add-handler :- ContextHandler
400 [webserver-context :- ServerContext
401 handler :- ContextHandler
402 enable-trailing-slash-redirect? :- schema/Bool]
403 (.setAllowNullPathInfo handler (not enable-trailing-slash-redirect?))
404 (.addHandler (:handlers webserver-context) handler)
405 ;; If this handler is being added after the server has been started, we
406 ;; need to mark the handler as "managed" so that the server will stop the
407 ;; handler when the server is stopped. We also need to manually start the
408 ;; handler. The server takes care of marking handlers as managed and starting
409 ;; them if the handlers are already registered when the server is started.
410 (if-let [server (.getServer handler)]
411 (when (and (.isRunning server) (not (.isRunning handler)))
412 (.manage (:handlers webserver-context) handler)
413 (.start handler)))
414 handler)
415
416 (defn- ring-handler
417 "Returns an Jetty Handler implementation for the given Ring handler."
418 [handler]
419 (proxy [AbstractHandler] []
420 (handle [_ ^Request base-request request response]
421 (let [request-map (servlet/build-request-map request)
422 response-map (handler request-map)]
423 (when response-map
424 (servlet/update-servlet-response response response-map)
425 (.setHandled base-request true))))))
426
427 (schema/defn ^:always-validate
428 proxy-servlet :- ProxyServlet
429 "Create an instance of Jetty's `ProxyServlet` that will proxy requests at
430 a given context to another host."
431 [webserver-context :- ServerContext
432 target :- ProxyTarget
433 options :- ProxyOptions]
434 (let [custom-ssl-ctxt-factory (when (map? (:ssl-config options))
435 (get-proxy-client-context-factory
436 (:ssl-config options)))
437 {:keys [request-buffer-size idle-timeout]} options]
438 (proxy [ProxyServlet] []
439 (rewriteURI [req]
440 (let [query (.getQueryString req)
441 scheme (let [target-scheme (:scheme options)]
442 (condp = target-scheme
443 nil (.getScheme req)
444 :orig (.getScheme req)
445 "orig" (.getScheme req)
446 :http "http"
447 :https "https"
448 "http" target-scheme
449 "https" target-scheme))
450 context-path (.getPathInfo req)]
451 (let [target-uri (URI. scheme
452 nil
453 (:host target)
454 (:port target)
455 (with-leading-slash
456 (URIUtil/addPaths (:path target) context-path))
457 (codec/url-decode (str query))
458 nil)]
459 (if-let [rewrite-uri-callback-fn (:rewrite-uri-callback-fn options)]
460 (rewrite-uri-callback-fn target-uri req)
461 target-uri))))
462
463 (newHttpClient []
464 (let [client (if custom-ssl-ctxt-factory
465 (HttpClient. custom-ssl-ctxt-factory)
466 (if-let [ssl-ctxt-factory (:ssl-context-factory
467 @(:state webserver-context))]
468 (HttpClient. ssl-ctxt-factory)
469 (HttpClient.)))]
470 (when request-buffer-size
471 (.setRequestBufferSize client request-buffer-size))
472 client))
473
474 (createHttpClient []
475 (let [client (proxy-super createHttpClient)
476 timeout (when idle-timeout
477 (* 1000 idle-timeout))]
478 (if (:follow-redirects options)
479 (.setFollowRedirects client true)
480 (.setFollowRedirects client false))
481 (when timeout
482 (.setIdleTimeout client timeout))
483 client))
484
485 (customizeProxyRequest [proxy-req req]
486 (if-let [callback-fn (:callback-fn options)]
487 (callback-fn proxy-req req)))
488
489 ;; The implementation of onResponseFailure is duplicated heavily from:
490 ;; https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java#L576-L607
491 ;; The only significant difference is that a 'failure-callback-fn', if
492 ;; defined in options, is invoked just prior to completing the async
493 ;; context for cases that the response was not already committed upstream.
494 (onResponseFailure [req resp proxy-resp failure]
495 (do
496 (let [request-id (.getRequestId this req)
497 async-context (.getAsyncContext req)]
498 (log/debug failure (i18n/trs "{0} proxying failed" request-id))
499 (if (.isCommitted resp)
500 (try
501 (.sendError resp -1)
502 (.complete async-context)
503 (catch IOException _
504 (log/debug failure (i18n/trs "{0} could not close the connection"
505 request-id))))
506 (do
507 (.resetBuffer resp)
508 (if (instance? TimeoutException failure)
509 (.setStatus resp HttpServletResponse/SC_GATEWAY_TIMEOUT)
510 (.setStatus resp HttpServletResponse/SC_BAD_GATEWAY))
511 (.setHeader resp
512 (.asString HttpHeader/CONNECTION)
513 (.asString HttpHeaderValue/CLOSE))
514 (when-let [failure-callback-fn (:failure-callback-fn options)]
515 (failure-callback-fn req resp proxy-resp failure))
516 (.complete async-context)))))))))
517
518 (schema/defn ^:always-validate
519 register-endpoint!
520 [state :- Atom
521 endpoint-map :- Endpoint
522 endpoint :- schema/Str]
523 (swap! state update-in [:endpoints endpoint] #(if (nil? %)
524 [endpoint-map]
525 (conj % endpoint-map))))
526
527 (schema/defn ^:always-validate max-request-body-size-handler*
528 [handler :- Handler
529 max-size :- schema/Int]
530 (proxy [HandlerWrapper] []
531 (handle [target ^Request base-request request response]
532 (let [request-size (.getContentLength base-request)]
533 (if (> request-size max-size)
534 (do
535 (.setStatus response HttpServletResponse/SC_REQUEST_ENTITY_TOO_LARGE)
536 (.setHandled base-request true))
537 (.handle handler target base-request request response))))))
538
539 (schema/defn ^:always-validate max-request-body-size-handler
540 "Wrap a max-request-body-size handler around the supplied handler. The
541 handler returns a 413 (request entity too large) error if the Content-Length
542 HTTP header on the incoming request exceeds the value specified as the
543 max-size parameter."
544 [handler :- Handler
545 max-size :- schema/Int]
546 (doto (max-request-body-size-handler* handler max-size)
547 (.setHandler handler)))
548
549 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
550 ;;; Public
551
552 (schema/defn ^:always-validate
553 initialize-context :- ServerContext
554 "Create a webserver-context which contains a HandlerCollection and a
555 ContextHandlerCollection which can accept the addition of new handlers
556 before the webserver is started."
557 []
558 (let [^ContextHandlerCollection chc (ContextHandlerCollection.)]
559 {:handlers chc
560 :state (atom {:endpoints {}
561 :mbean-container nil
562 :overrides-read-by-webserver false
563 :overrides nil
564 :ssl-context-factory nil})
565 :server nil}))
566
567 ; TODO move out of public
568 (schema/defn ^:always-validate
569 merge-webserver-overrides-with-options :- config/WebserverRawConfig
570 "Merge any overrides made to the webserver config settings with the supplied
571 options."
572 [webserver-context :- ServerContext
573 options :- config/WebserverRawConfig]
574 {:post [(map? %)]}
575 (let [overrides (:overrides (swap! (:state webserver-context)
576 assoc
577 :overrides-read-by-webserver
578 true))]
579 (doseq [key (keys overrides)]
580 (log/info (i18n/trs "webserver config overridden for key ''{0}''" (name key))))
581 (merge options overrides)))
582
583 (schema/defn ^:always-validate shutdown
584 [{:keys [server] :as webserver-context} :- ServerContext]
585 (when-let [mbean-container (:mbean-container @(:state webserver-context))]
586 (log/debug (i18n/trs "Cleaning up JMX MBean container"))
587 (.destroy mbean-container)
588 (swap! (:state webserver-context) assoc :mbean-container nil))
589 (when (started? webserver-context)
590 (log/info (i18n/trs "Shutting down web server."))
591 (try
592 (.stop server)
593 (catch TimeoutException e
594 (log/error e (i18n/trs "Web server failed to shut down gracefully in configured timeout period ({0}); cancelling remaining requests."
595 (.getStopTimeout server)))))
596 (log/info (i18n/trs "Web server shutdown"))))
597
598 (schema/defn ^:always-validate
599 create-webserver :- ServerContext
600 "Create a Jetty webserver according to the supplied options:
601
602 :host - the hostname to listen on
603 :port - the port to listen on (defaults to 8080)
604 :ssl-host - the hostname to listen on for SSL connections
605 :ssl-port - the SSL port to listen on (defaults to 8081)
606 :max-threads - the maximum number of threads to use (default 100)
607 :request-header-max-size - the maximum size of an HTTP request header (default 8192)
608 :gzip-enable - whether or not gzip compression can be applied
609 to the body of a response (default true)
610
611 SSL may be configured via PEM files by providing all three of the following
612 settings:
613
614 :ssl-key - a PEM file containing the host's private key
615 :ssl-cert - a PEM file containing the host's certificate or chain
616 :ssl-ca-cert - a PEM file containing the CA certificate
617
618 or via JKS keystore files by providing all four of the following settings:
619
620 :keystore - the keystore to use for SSL connections
621 :key-password - the password to the keystore
622 :truststore - a truststore to use for SSL connections
623 :trust-password - the password to the truststore
624
625 Note that if SSL is being configured via PEM files, an optional
626 :ssl-cert-chain setting may be included to specify a supplemental set
627 of certificates to be appended to the first certificate from the :ssl-cert
628 setting in order to construct the certificate chain.
629
630 Other SSL settings:
631
632 :client-auth - SSL client certificate authenticate, may be set to :need,
633 :want or :none (defaults to :need)
634 :cipher-suites - list of cryptographic ciphers to allow for incoming SSL connections
635 :ssl-protocols - list of protocols to allow for incoming SSL connections"
636 [webserver-context :- ServerContext
637 options :- config/WebserverRawConfig]
638 {:pre [(map? options)]
639 :post [(started? %)]}
640 (let [config (config/process-config
641 (merge-webserver-overrides-with-options
642 webserver-context
643 options))
644 ^Server s (create-server webserver-context config)
645 ^HandlerCollection hc (HandlerCollection.)
646 log-handler (config/maybe-init-log-handler options)]
647 (.setHandlers hc (into-array Handler [(:handlers webserver-context)]))
648 (let [shutdown-timeout (when (not (nil? (:shutdown-timeout-seconds options)))
649 (* 1000 (:shutdown-timeout-seconds options)))
650 maybe-zipped (if (:gzip-enable options true)
651 (gzip-handler hc)
652 hc)
653 maybe-size-restricted (if-let [max-size (:request-body-max-size
654 options)]
655 (max-request-body-size-handler
656 maybe-zipped
657 max-size)
658 maybe-zipped)
659 maybe-logged (if log-handler
660 (doto log-handler (.setHandler maybe-size-restricted))
661 maybe-size-restricted)
662 statistics-handler (if (or (nil? shutdown-timeout) (pos? shutdown-timeout))
663 (doto (StatisticsHandler.)
664 (.setHandler maybe-logged))
665 maybe-logged)]
666 (.setHandler s statistics-handler)
667 (when shutdown-timeout
668 (.setStopTimeout s shutdown-timeout))
669 (when-let [script (:post-config-script options)]
670 (config/execute-post-config-script! s script))
671 (assoc webserver-context :server s))))
672
673 (schema/defn ^:always-validate start-webserver! :- ServerContext
674 "Creates and starts a webserver. Returns an updated context map containing
675 the Server object."
676 [webserver-context :- ServerContext
677 config :- config/WebserverRawConfig]
678 (let [webserver-context (create-webserver webserver-context config)]
679 (log/info (i18n/trs "Starting web server."))
680 (try
681 (.start (:server webserver-context))
682 (catch Exception e
683 (log/error e (i18n/trs "Encountered error starting web server, so shutting down"))
684 (shutdown webserver-context)
685 (throw e)))
686 webserver-context))
687
688 (schema/defn ^:always-validate
689 add-context-handler :- ContextHandler
690 "Add a static content context handler (allow for customization of the context handler through javax.servlet.ServletContextListener implementations)"
691 ([webserver-context base-path context-path]
692 (add-context-handler webserver-context base-path context-path nil {:follow-links? false
693 :enable-trailing-slash-redirect? false}))
694 ([webserver-context :- ServerContext
695 base-path :- schema/Str
696 context-path :- schema/Str
697 context-listeners :- (schema/maybe [ServletContextListener])
698 options]
699 (let [handler (ServletContextHandler. nil context-path ServletContextHandler/NO_SESSIONS)
700 follow-links? (:follow-links? options)
701 enable-trailing-slash-redirect? (:enable-trailing-slash-redirect? options)
702 normalize-request-uri? (:normalize-request-uri? options)]
703 (.setBaseResource handler (Resource/newResource base-path))
704 (when follow-links?
705 (.addAliasCheck handler (AllowSymLinkAliasChecker.)))
706 ;; register servlet context listeners (if any)
707 (when-not (nil? context-listeners)
708 (dorun (map #(.addEventListener handler %) context-listeners)))
709 (.addServlet handler (ServletHolder. (DefaultServlet.)) "/")
710 (when normalize-request-uri?
711 (normalized-uri-helpers/add-normalized-uri-filter-to-servlet-handler!
712 handler))
713 (add-handler webserver-context handler enable-trailing-slash-redirect?))))
714
715 (schema/defn ^:always-validate
716 add-ring-handler :- ContextHandler
717 [webserver-context :- ServerContext
718 handler :- (schema/pred ifn? 'ifn?)
719 path :- schema/Str
720 enable-trailing-slash-redirect? :- schema/Bool
721 normalize-request-uri? :- schema/Bool]
722 (let [handler
723 (normalized-uri-helpers/handler-maybe-wrapped-with-normalized-uri
724 (ring-handler handler)
725 normalize-request-uri?)
726 ctxt-handler (doto (ContextHandler. path)
727 (.setHandler handler))]
728 (add-handler webserver-context ctxt-handler enable-trailing-slash-redirect?)))
729
730 (schema/defn ^:always-validate
731 add-websocket-handler :- ContextHandler
732 [webserver-context :- ServerContext
733 handlers :- websockets/WebsocketHandlers
734 path :- schema/Str
735 enable-trailing-slash-redirect? :- schema/Bool
736 normalize-request-uri? :- schema/Bool]
737 (let [handler
738 (normalized-uri-helpers/handler-maybe-wrapped-with-normalized-uri
739 (websockets/websocket-handler handlers)
740 normalize-request-uri?)
741 ctxt-handler (doto (ContextHandler. path)
742 (.setHandler handler))]
743 (add-handler webserver-context ctxt-handler enable-trailing-slash-redirect?)))
744
745 (schema/defn ^:always-validate
746 add-servlet-handler :- ContextHandler
747 [webserver-context :- ServerContext
748 servlet :- Servlet
749 path :- schema/Str
750 servlet-init-params :- {schema/Any schema/Any}
751 enable-trailing-slash-redirect? :- schema/Bool
752 normalize-request-uri? :- schema/Bool]
753 (let [holder (doto (ServletHolder. servlet)
754 (.setInitParameters servlet-init-params))
755 handler (doto (ServletContextHandler. ServletContextHandler/SESSIONS)
756 (.setContextPath path)
757 (.addServlet holder "/*"))]
758 (when normalize-request-uri?
759 (normalized-uri-helpers/add-normalized-uri-filter-to-servlet-handler!
760 handler))
761 (add-handler webserver-context handler enable-trailing-slash-redirect?)))
762
763 (schema/defn ^:always-validate
764 add-war-handler :- ContextHandler
765 "Registers a WAR to Jetty. It takes two arguments: `[war path]`.
766 - `war` is the file path or the URL to a WAR file
767 - `path` is the URL prefix at which the WAR will be registered"
768 [webserver-context :- ServerContext
769 war :- schema/Str
770 path :- schema/Str
771 disable-redirects-no-slash? :- schema/Bool
772 normalize-request-uri? :- schema/Bool]
773 (let [handler (doto (WebAppContext.)
774 (.setContextPath path)
775 (.setWar war))]
776 (when normalize-request-uri?
777 (normalized-uri-helpers/add-normalized-uri-filter-to-servlet-handler!
778 handler))
779 (add-handler webserver-context handler disable-redirects-no-slash?)))
780
781 (schema/defn ^:always-validate
782 add-proxy-route
783 "Configures the Jetty server to proxy a given URL path to another host.
784
785 `target` should be a map containing the keys :host, :port, and :path; where
786 :path specifies the URL prefix to proxy to on the target host.
787
788 `options` may contain the keys :scheme (legal values are :orig, :http, and
789 :https), :ssl-config (value may be :use-server-config or a map containing
790 :ssl-ca-cert, :ssl-cert, and :ssl-key), :rewrite-uri-callback-fn (a function
791 taking two arguments, `[target-uri req]`, see README.md/#rewrite-uri-callback-fn),
792 :callback-fn (a function taking two arguments, `[proxy-req req]`, see
793 README.md/#callback-fn) and :failure-callback-fn (a function taking four arguments,
794 `[req resp proxy-resp failure]`, see README.md/#failure-callback-fn).
795 "
796 [webserver-context :- ServerContext
797 target :- ProxyTarget
798 path :- schema/Str
799 options :- ProxyOptions
800 disable-redirects-no-slash? :- schema/Bool]
801 (let [target (update-in target [:path] remove-leading-slash)]
802 ;; This call hardcodes a value of 'false' for the 'normalize-request-uri?'
803 ;; parameter because the ProxyServlet already has its own logic for
804 ;; normalizing request URIs as it proxies them through. Applying an
805 ;; extra layer of normalization in front of the ProxyServlet might otherwise
806 ;; cause requests to be proxied to an unintended URI.
807 (add-servlet-handler webserver-context
808 (proxy-servlet webserver-context target options)
809 path
810 {}
811 disable-redirects-no-slash?
812 false)))
813
814 (schema/defn ^:always-validate
815 get-registered-endpoints :- RegisteredEndpoints
816 "Returns a map of registered endpoints for the given ServerContext.
817 Each endpoint is registered as a key in the map, with its value
818 being an array of maps, each representing a handler registered
819 at that endpoint. Each of these maps contains the type of the
820 handler under the :type key, and may contain additional information
821 as well.
822
823 When the value of :type is :context, the endpoint information will
824 be an instance of ContextEndpoint.
825
826 When the value of :type is :ring, the endpoint information will be
827 an instance of RingEndpoint.
828
829 When the value of :type is :servlet, the endpoint information will
830 be an instance of ServletEndpoint.
831
832 When the value of :type is :war, the endpoint information will be
833 an instance of WarEndpoint.
834
835 When the value of :type is :proxy, the endpoint information will be
836 an instance of ProxyEndpoint."
837 [webserver-context :- ServerContext]
838 (:endpoints @(:state webserver-context)))
839
840 (schema/defn ^:always-validate
841 override-webserver-settings! :- config/WebserverRawConfig
842 "Override the settings in the webserver section of the service's config file
843 with the set of options in the supplied overrides map.
844
845 The map should contain a key/value pair for each setting to be overridden.
846 The name of the setting to override should be expressed as a Clojure keyword.
847 For any setting expressed in the service config which is not overridden, the
848 setting value from the config will be used.
849
850 For example, the webserver config may contain:
851
852 [webserver]
853 ssl-host = 0.0.0.0
854 ssl-port = 9001
855 ssl-cert = mycert.pem
856 ssl-key = mykey.pem
857 ssl-ca-cert = myca.pem
858
859 The following overrides map may be supplied as an argument to the function:
860
861 {:ssl-port 9002
862 :ssl-cert \"myoverriddencert.pem\"
863 :ssl-key \"myoverriddenkey.pem\"}
864
865 The effective settings used during webserver startup will be:
866
867 {:ssl-host \"0.0.0.0\"
868 :ssl-port 9002
869 :ssl-cert \"myoverriddencert.pem\"
870 :ssl-key \"myoverriddenkey.pem\"
871 :ssl-ca-cert \"myca.pem\"}
872
873 The overridden webserver settings will be considered only at the point the
874 webserver is being started -- during the start lifecycle phase of the
875 webserver service. For this reason, a call to this function must be made
876 during a service's init lifecycle phase in order for the overridden
877 settings to be considered.
878
879 Only one call from a service may be made to this function during application
880 startup.
881
882 If a call is made to this function after webserver startup or after another
883 call has already been made to this function (e.g., from other service),
884 a java.lang.IllegalStateException will be thrown."
885 [webserver-context :- ServerContext
886 overrides :- config/WebserverRawConfig]
887 ; Might be worth considering an implementation that only fails if the caller
888 ; is trying to override a specific option that has been overridden already
889 ; rather than blindly failing if an attempt is made to override any option.
890 ; Could allow different services to override options that don't conflict with
891 ; one another or for the same service to make multiple calls to this function
892 ; for different settings. Hard to know, though, when one setting has an
893 ; adverse effect on another without putting a bunch of key-specific semantic
894 ; setting parsing in this implementation.
895 (:overrides
896 (swap! (:state webserver-context)
897 #(cond
898 (:overrides-read-by-webserver %)
899 (if (nil? (:overrides %))
900 (throw
901 (IllegalStateException.
902 (i18n/trs "overrides cannot be set because webserver has already processed the config")))
903 (throw
904 (IllegalStateException.
905 (i18n/trs "overrides cannot be set because they have already been set and webserver has already processed the config"))))
906 (nil? (:overrides %))
907 (assoc % :overrides overrides)
908 :else
909 (throw
910 (IllegalStateException.
911 (i18n/trs "overrides cannot be set because they have already been set")))))))
912
913 (schema/defn ^:always-validate join
914 [webserver-context :- ServerContext]
915 {:pre [(started? webserver-context)]}
916 (.join (:server webserver-context)))
917
918 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
919 ;;; Service Implementation Helper Functions
920
921 (defn start-server-single-default
922 [context config]
923 (let [default-context (:default (:jetty9-servers context))
924 webserver (start-webserver! default-context config)
925 server-context-list (assoc (:jetty9-servers context) :default webserver)]
926 (assoc context :jetty9-servers server-context-list)))
927
928 (defn start-server-multiple
929 [context config]
930 (let [context-seq (for [[server-id server-context] (:jetty9-servers context)]
931 [server-id (start-webserver! server-context (server-id config))])]
932 (assoc context :jetty9-servers (into {} context-seq))))
933
934 (defn get-server-context
935 [service-context server-id]
936 (let [server-id (if (nil? server-id)
937 (:default-server service-context)
938 server-id)]
939 (when-not server-id
940 (throw (IllegalArgumentException.
941 (i18n/trs "no server-id was specified for this operation and no default server was specified in the configuration"))))
942 (server-id (:jetty9-servers service-context))))
943
944 (defn get-default-server-from-config
945 [config]
946 (first (flatten (filter #(:default-server (second %)) config))))
947
948 (defn build-server-contexts
949 [context config]
950 (assoc context :jetty9-servers (into {} (for [[server-id] config]
951 [server-id (initialize-context)]))
952 :default-server (get-default-server-from-config config)))
953
954 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
955 ;;; Service Function Implementations
956
957 (schema/defn ^:always-validate add-context-handler!
958 [context base-path context-path options :- ContextHandlerOptions]
959 (let [defaults {:context-listeners []}
960 opts (merge defaults options)
961 server-id (:server-id opts)
962 context-listeners (:context-listeners opts)
963 s (get-server-context context server-id)
964 state (:state s)
965 endpoint-map {:type :context
966 :base-path base-path
967 :context-listeners context-listeners}
968 enable-redirect (get options :redirect-if-no-trailing-slash false)
969 normalize-request-uri (get options :normalize-request-uri false)]
970 (register-endpoint! state endpoint-map context-path)
971 (add-context-handler s base-path context-path
972 context-listeners
973 {:follow-links? (:follow-links options)
974 :enable-trailing-slash-redirect? enable-redirect
975 :normalize-request-uri? normalize-request-uri})))
976
977 (schema/defn ^:always-validate init!
978 [context config :- config/WebserverServiceRawConfig]
979 (let [old-config (schema/check config/WebserverRawConfig config)
980 new-config (schema/check config/MultiWebserverRawConfig config)]
981 (cond
982 (nil? old-config)
983 (let [context (assoc context :jetty9-servers {:default (initialize-context)}
984 :default-server :default)]
985 (doseq [content (:static-content config)]
986 (add-context-handler! context (:resource content)
987 (:path content) {:follow-links (true? (:follow-links content))}))
988 context)
989 (nil? new-config)
990 (let [context (build-server-contexts context config)]
991 (doseq [[server-id server-config] config
992 content (:static-content server-config)]
993 (add-context-handler! context (:resource content)
994 (:path content) {:server-id server-id
995 :follow-links (true? (:follow-links content))}))
996 context))))
997
998 (schema/defn ^:always-validate start!
999 [context config :- config/WebserverServiceRawConfig]
1000 (let [old-config (schema/check config/WebserverRawConfig config)
1001 new-config (schema/check config/MultiWebserverRawConfig config)]
1002 (cond
1003 (nil? old-config) (start-server-single-default context config)
1004 (nil? new-config) (start-server-multiple context config))))
1005
1006 (schema/defn ^:always-validate add-ring-handler!
1007 [context handler path options :- CommonOptions]
1008 (let [server-id (:server-id options)
1009 s (get-server-context context server-id)
1010 state (:state s)
1011 endpoint-map {:type :ring}
1012
1013 enable-redirect (get options :redirect-if-no-trailing-slash false)
1014 normalize-request-uri (get options :normalize-request-uri false)]
1015 (register-endpoint! state endpoint-map path)
1016 (add-ring-handler s handler path enable-redirect normalize-request-uri)))
1017
1018 (schema/defn ^:always-validate add-websocket-handler!
1019 [context
1020 handlers :- websockets/WebsocketHandlers
1021 path :- schema/Str
1022 options :- CommonOptions]
1023 (let [server-id (:server-id options)
1024 s (get-server-context context server-id)
1025 state (:state s)
1026 endpoint-map {:type :websocket}
1027 enable-redirect (get options :redirect-if-no-trailing-slash false)
1028 normalize-request-uri (get options :normalize-request-uri false)]
1029 (register-endpoint! state endpoint-map path)
1030 (add-websocket-handler s
1031 handlers
1032 path
1033 enable-redirect
1034 normalize-request-uri)))
1035
1036 (schema/defn ^:always-validate add-servlet-handler!
1037 [context servlet path options :- ServletHandlerOptions]
1038 (let [defaults {:servlet-init-params {}}
1039 opts (merge defaults options)
1040 server-id (:server-id opts)
1041 servlet-init-params (:servlet-init-params opts)
1042 s (get-server-context context server-id)
1043 state (:state s)
1044 endpoint-map {:type :servlet
1045 :servlet (type servlet)}
1046
1047 enable-redirect (get options :redirect-if-no-trailing-slash false)
1048 normalize-request-uri (get options :normalize-request-uri false)]
1049 (register-endpoint! state endpoint-map path)
1050 (add-servlet-handler s
1051 servlet
1052 path
1053 servlet-init-params
1054 enable-redirect
1055 normalize-request-uri)))
1056
1057 (schema/defn ^:always-validate add-war-handler!
1058 [context war path options :- CommonOptions]
1059 (let [server-id (:server-id options)
1060 s (get-server-context context server-id)
1061 state (:state s)
1062 endpoint-map {:type :war
1063 :war-path war}
1064
1065 enable-redirect (get options :redirect-if-no-trailing-slash false)
1066 normalize-request-uri (get options :normalize-request-uri false)]
1067 (register-endpoint! state endpoint-map path)
1068 (add-war-handler s war path enable-redirect normalize-request-uri)))
1069
1070 (schema/defn ^:always-validate add-proxy-route!
1071 [context target path options]
1072 (let [server-id (:server-id options)
1073 s (get-server-context context server-id)
1074 state (:state s)
1075 endpoint-map {:type :proxy
1076 :target-host (:host target)
1077 :target-port (:port target)
1078 :target-path (:path target)}
1079 enable-redirect (get options :redirect-if-no-trailing-slash false)]
1080 (register-endpoint! state endpoint-map path)
1081 (add-proxy-route s target path options enable-redirect)))
0 (ns puppetlabs.trapperkeeper.services.webserver.jetty9-service
1 (:import (org.eclipse.jetty.jmx MBeanContainer))
2 (:require
3 [clojure.tools.logging :as log]
4
5 [puppetlabs.trapperkeeper.services.webserver.jetty9-config :as config]
6 [puppetlabs.trapperkeeper.services.webserver.jetty9-core :as core]
7 [puppetlabs.trapperkeeper.services :refer [service-context]]
8 [puppetlabs.trapperkeeper.core :refer [defservice]]
9 [puppetlabs.i18n.core :as i18n]))
10
11 ;; TODO: this should probably be moved to a separate jar that can be used as
12 ;; a dependency for all webserver service implementations
13 (defprotocol WebserverService
14 (add-context-handler [this base-path context-path] [this base-path context-path options])
15 (add-ring-handler [this handler path] [this handler path options])
16 (add-websocket-handler [this handlers path] [this handler path options])
17 (add-servlet-handler [this servlet path] [this servlet path options])
18 (add-war-handler [this war path] [this war path options])
19 (add-proxy-route [this target path] [this target path options])
20 (override-webserver-settings! [this overrides] [this server-id overrides])
21 (get-registered-endpoints [this] [this server-id])
22 (log-registered-endpoints [this] [this server-id])
23 (join [this] [this server-id]))
24
25 (defservice jetty9-service
26 "Provides a Jetty 9 web server as a service"
27 WebserverService
28 [[:ConfigService get-in-config]]
29 (init [this context]
30 (log/info (i18n/trs "Initializing web server(s)."))
31 (let [config (or (get-in-config [:webserver])
32 ;; Here for backward compatibility with existing projects
33 (get-in-config [:jetty])
34 {})]
35 (config/validate-config config)
36 (core/init! context config)))
37
38 (start [this context]
39 (log/info (i18n/trs "Starting web server(s)."))
40 (let [config (or (get-in-config [:webserver])
41 ;; Here for backward compatibility with existing projects
42 (get-in-config [:jetty])
43 {})]
44 (core/start! context config)))
45
46 (stop [this context]
47 (log/info (i18n/trs "Shutting down web server(s)."))
48 (doseq [key (keys (:jetty9-servers context))]
49 (if-let [server (key (:jetty9-servers context))]
50 (core/shutdown server)))
51 ;; this class leaks MBean names if this method is not called
52 (MBeanContainer/resetUnique)
53 context)
54
55 (add-context-handler [this base-path context-path]
56 (core/add-context-handler! (service-context this) base-path context-path {}))
57
58 (add-context-handler [this base-path context-path options]
59 (core/add-context-handler! (service-context this) base-path context-path options))
60
61 (add-ring-handler [this handler path]
62 (core/add-ring-handler! (service-context this) handler path {}))
63
64 (add-ring-handler [this handler path options]
65 (core/add-ring-handler! (service-context this) handler path options))
66
67 (add-websocket-handler [this handlers path]
68 (core/add-websocket-handler! (service-context this) handlers path {}))
69
70 (add-websocket-handler [this handlers path options]
71 (core/add-websocket-handler! (service-context this) handlers path options))
72
73 (add-servlet-handler [this servlet path]
74 (core/add-servlet-handler! (service-context this) servlet path {}))
75
76 (add-servlet-handler [this servlet path options]
77 (core/add-servlet-handler! (service-context this) servlet path options))
78
79 (add-war-handler [this war path]
80 (core/add-war-handler! (service-context this) war path {}))
81
82 (add-war-handler [this war path options]
83 (core/add-war-handler! (service-context this) war path options))
84
85 (add-proxy-route [this target path]
86 (core/add-proxy-route! (service-context this) target path {}))
87
88 (add-proxy-route [this target path options]
89 (core/add-proxy-route! (service-context this) target path options))
90
91 (override-webserver-settings! [this overrides]
92 (let [s (core/get-server-context (service-context this) nil)]
93 (core/override-webserver-settings! s overrides)))
94
95 (override-webserver-settings! [this server-id overrides]
96 (let [s (core/get-server-context (service-context this) server-id)]
97 (core/override-webserver-settings! s overrides)))
98
99 (get-registered-endpoints [this]
100 (let [s (core/get-server-context (service-context this) nil)]
101 (core/get-registered-endpoints s)))
102
103 (get-registered-endpoints [this server-id]
104 (let [s (core/get-server-context (service-context this) server-id)]
105 (core/get-registered-endpoints s)))
106 (log-registered-endpoints [this]
107 (log/info (str (get-registered-endpoints this))))
108
109 (log-registered-endpoints[this server-id]
110 (log/info (str (get-registered-endpoints this server-id))))
111
112 (join [this]
113 (let [s (core/get-server-context (service-context this) nil)]
114 (core/join s)))
115
116 (join [this server-id]
117 (let [s (core/get-server-context (service-context this) server-id)]
118 (core/join s))))
0 (ns puppetlabs.trapperkeeper.services.webserver.normalized-uri-helpers
1 (:import (javax.servlet.http HttpServletRequest HttpServletResponse)
2 (org.eclipse.jetty.util URIUtil)
3 (org.eclipse.jetty.server Request)
4 (org.eclipse.jetty.server.handler HandlerWrapper AbstractHandler)
5 (com.puppetlabs.trapperkeeper.services.webserver.jetty9.utils
6 HttpServletRequestWithAlternateRequestUri)
7 (javax.servlet Filter DispatcherType)
8 (java.util EnumSet)
9 (org.eclipse.jetty.servlet FilterHolder ServletContextHandler))
10 (:require [schema.core :as schema]
11 [ring.util.servlet :as servlet]
12 [clojure.string :as str]
13 [puppetlabs.i18n.core :as i18n]))
14
15 (schema/defn ^:always-validate normalize-uri-path :- schema/Str
16 "Return a 'normalized' version of the uri path represented on the incoming
17 request. The 'normalization' consists of three steps:
18
19 1) URL (percent) decode the path, assuming any percent-encodings represent
20 UTF-8 characters.
21
22 An exception may be thrown if the request has malformed content, e.g.,
23 partially-formed percent-encoded characters like '%A%B'.
24
25 If a semicolon character, U+003B, is found during the decoding process, it
26 and any following characters will be removed from the decoded path.
27
28 2) Check the percent-decoded path for any relative path segments ('..' or
29 '.').
30
31 An IllegalArgumentException is thrown if one or more segments are found.
32
33 3) Compact any repeated forward slash characters in a path."
34 [request :- HttpServletRequest]
35 (let [percent-decoded-uri-path (-> request
36 (.getRequestURI)
37 (URIUtil/decodePath))
38 canonicalized-uri-path (URIUtil/canonicalPath percent-decoded-uri-path)]
39 (if (or (nil? canonicalized-uri-path)
40 (not= (.length percent-decoded-uri-path)
41 (.length canonicalized-uri-path)))
42 (throw (IllegalArgumentException.
43 (i18n/trs "Invalid relative path (.. or .) in: {0}"
44 percent-decoded-uri-path)))
45 (URIUtil/compactPath canonicalized-uri-path))))
46
47 (schema/defn ^:always-validate
48 normalize-uri-handler :- HandlerWrapper
49 "Create a `HandlerWrapper` which provides a normalized request URI on to
50 its downstream handler for an incoming request. The normalized URI would
51 be returned for a 'getRequestURI' call made by the downstream handler on
52 its incoming HttpServletRequest request parameter. Normalization is done
53 per the rules described in the `normalize-uri-path` function. If an error
54 is encountered during request URI normalization, an HTTP 400 (Bad Request)
55 response is returned rather than the request being passed on its downstream
56 handler."
57 []
58 (proxy [HandlerWrapper] []
59 (handle [^String target ^Request base-request
60 ^HttpServletRequest request ^HttpServletResponse response]
61 (let [handler (proxy-super getHandler)
62 is-started (proxy-super isStarted)]
63 ;; It may not strictly be necessary to check if the wrapping handler
64 ;; is started in order to let a request through but that's what the
65 ;; base `HandlerWrapper` class from Jetty does, so it seemed best to
66 ;; follow the pattern:
67 ;; https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java#L95
68 (when (and handler is-started)
69 (if-let [normalized-uri
70 (try
71 (normalize-uri-path request)
72 (catch IllegalArgumentException ex
73 (do
74 (servlet/update-servlet-response
75 response
76 {:status 400
77 :body (.getMessage ex)})
78 (.setHandled base-request true))
79 nil))]
80 (.handle
81 handler
82 target
83 base-request
84 (HttpServletRequestWithAlternateRequestUri.
85 request
86 normalized-uri)
87 response)))))))
88
89 (schema/defn ^:always-validate normalized-uri-filter :- Filter
90 "Create a servlet filter which provides a normalized request URI on to its
91 downstream consumers for an incoming request. The normalized URI would be
92 returned for a 'getRequestURI' call on the HttpServletRequest parameter.
93 Normalization is done per the rules described in the `normalize-uri-path`
94 function. If an error is encountered during request URI normalization, an
95 HTTP 400 (Bad Request) response is returned rather than the request being
96 passed on its downstream consumers."
97 []
98 (reify Filter
99 (init [_ _])
100 (doFilter [_ request response chain]
101 ;; The method signature for a servlet filter has a 'request' of the
102 ;; more generic 'ServletRequest' and 'response' of the more generic
103 ;; 'ServletResponse'. While we practically shouldn't see anything
104 ;; but the more specific Http types for each, this code explicitly
105 ;; checks to see that the requests are Http types as the URI
106 ;; normalization would be irrelevant for other types.
107 (if (and (instance? HttpServletRequest request)
108 (instance? HttpServletResponse response))
109 (if-let [normalized-uri
110 (try
111 (normalize-uri-path request)
112 (catch IllegalArgumentException ex
113 (servlet/update-servlet-response
114 response
115 {:status 400
116 :body (.getMessage ex)})
117 nil))]
118 (.doFilter chain
119 (HttpServletRequestWithAlternateRequestUri.
120 request
121 normalized-uri)
122 response))
123 (.doFilter chain request response)))
124 (destroy [_])))
125
126 (schema/defn ^:always-validate
127 add-normalized-uri-filter-to-servlet-handler!
128 "Adds a servlet filter to the servlet handler which provides a normalized
129 request URI on to its downstream consumers for an incoming request."
130 [handler :- ServletContextHandler]
131 (let [filter-holder (FilterHolder. (normalized-uri-filter))]
132 (.addFilter handler
133 filter-holder
134 "/*"
135 (EnumSet/of DispatcherType/REQUEST))))
136
137 (schema/defn ^:always-validate
138 handler-maybe-wrapped-with-normalized-uri :- AbstractHandler
139 "If the supplied `normalize-request-uri?` parameter is 'true', return a
140 handler that normalizes a request uri before passing it on downstream to
141 the supplied handler for an incoming request. If the supplied
142 `normalize-request-uri?` is 'false', return the supplied handler."
143 [handler :- AbstractHandler
144 normalize-request-uri? :- schema/Bool]
145 (if normalize-request-uri?
146 (doto (normalize-uri-handler)
147 (.setHandler handler))
148 handler))
0 (ns puppetlabs.trapperkeeper.services.webrouting.webrouting-service-handlers-test
1 (:import (servlet SimpleServlet))
2 (:require [clojure.test :refer :all]
3 [schema.test :as schema-test]
4 [puppetlabs.trapperkeeper.services :as tk-services]
5 [puppetlabs.trapperkeeper.services.webrouting.webrouting-service :refer :all]
6 [puppetlabs.trapperkeeper.services.webserver.jetty9-service :refer [jetty9-service]]
7 [puppetlabs.trapperkeeper.app :refer [get-service]]
8 [puppetlabs.trapperkeeper.testutils.webrouting.common :refer :all]
9 [puppetlabs.trapperkeeper.testutils.bootstrap :refer [with-app-with-config]]
10 [puppetlabs.trapperkeeper.testutils.logging
11 :refer [with-test-logging]]
12 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]))
13
14 (use-fixtures :once
15 schema-test/validate-schemas
16 testutils/assert-clean-shutdown)
17
18 (def dev-resources-dir "./dev-resources/")
19
20 (defprotocol TestDummy
21 (dummy [this]))
22
23 (tk-services/defservice test-dummy
24 TestDummy
25 []
26 (dummy [this]
27 "This is a dummy function. Please ignore."))
28
29 (def webrouting-plaintext-multiserver-config
30 {:webserver {:bar {:port 8080
31 :default-server true}
32 :foo {:port 9000}}
33 :web-router-service
34 {:puppetlabs.trapperkeeper.services.webrouting.webrouting-service-handlers-test/test-dummy
35 {:route "/foo"
36 :server "foo"}}})
37
38 (def webrouting-plaintext-multiroute-config
39 {:webserver {:port 8080}
40 :web-router-service
41 {:puppetlabs.trapperkeeper.services.webrouting.webrouting-service-handlers-test/test-dummy
42 {:quux "/foo"
43 :foo "/bar"}}})
44
45 (deftest add-context-handler-test
46 (testing "static content context with web routing"
47 (with-app-with-config app
48 [jetty9-service
49 webrouting-service
50 test-dummy]
51 webrouting-plaintext-config
52 (let [s (get-service app :WebroutingService)
53 add-context-handler (partial add-context-handler s)
54 resource "logback.xml"
55 svc (get-service app :TestDummy)]
56 (add-context-handler svc dev-resources-dir)
57 (let [response (http-get (str "http://localhost:8080/foo/" resource))]
58 (is (= (:status response) 200))
59 (is (= (:body response) (slurp (str dev-resources-dir resource))))))))
60
61 (testing "static content context with multiple routes"
62 (with-app-with-config app
63 [jetty9-service
64 webrouting-service
65 test-dummy]
66 webrouting-plaintext-multiroute-config
67 (let [s (get-service app :WebroutingService)
68 add-context-handler (partial add-context-handler s)
69 resource "logback.xml"
70 svc (get-service app :TestDummy)]
71 (add-context-handler svc dev-resources-dir {:route-id :quux})
72 (add-context-handler svc dev-resources-dir {:route-id :foo})
73 (let [response (http-get (str "http://localhost:8080/foo/" resource))]
74 (is (= (:status response) 200))
75 (is (= (:body response) (slurp (str dev-resources-dir resource)))))
76 (let [response (http-get (str "http://localhost:8080/bar/" resource))]
77 (is (= (:status response) 200))
78 (is (= (:body response) (slurp (str dev-resources-dir resource)))))))))
79
80 (deftest ring-handler-test-web-routing
81 (testing "ring request over http succeeds with web-routing"
82 (with-app-with-config app
83 [jetty9-service
84 webrouting-service
85 test-dummy]
86 webrouting-plaintext-config
87 (let [s (get-service app :WebroutingService)
88 add-ring-handler (partial add-ring-handler s)
89 body "Hi World"
90 ring-handler (fn [req] {:status 200 :body body})
91 svc (get-service app :TestDummy)]
92 (add-ring-handler svc ring-handler)
93 (let [response (http-get "http://localhost:8080/foo")]
94 (is (= (:status response) 200))
95 (is (= (:body response) body))))))
96
97 (testing "ring request over http succeeds with multiple web-routes"
98 (with-app-with-config app
99 [jetty9-service
100 webrouting-service
101 test-dummy]
102 webrouting-plaintext-multiroute-config
103 (let [s (get-service app :WebroutingService)
104 add-ring-handler (partial add-ring-handler s)
105 body "Hi World"
106 ring-handler (fn [req] {:status 200 :body body})
107 svc (get-service app :TestDummy)]
108 (add-ring-handler svc ring-handler {:route-id :quux})
109 (add-ring-handler svc ring-handler {:route-id :foo})
110 (let [response (http-get "http://localhost:8080/foo")]
111 (is (= (:status response) 200))
112 (is (= (:body response) body)))
113 (let [response (http-get "http://localhost:8080/bar")]
114 (is (= (:status response) 200))
115 (is (= (:body response) body)))))))
116
117 (deftest servlet-test-web-routing
118 (testing "request to servlet over http succeeds with web routing"
119 (with-app-with-config app
120 [jetty9-service
121 webrouting-service
122 test-dummy]
123 webrouting-plaintext-config
124 (let [s (get-service app :WebroutingService)
125 add-servlet-handler (partial add-servlet-handler s)
126 body "Hey there"
127 servlet (SimpleServlet. body)
128 svc (get-service app :TestDummy)]
129 (add-servlet-handler svc servlet)
130 (let [response (http-get "http://localhost:8080/foo")]
131 (is (= (:status response) 200))
132 (is (= (:body response) body))))))
133
134 (testing "request to servlet over http succeeds with multiple web routes"
135 (with-app-with-config app
136 [jetty9-service
137 webrouting-service
138 test-dummy]
139 webrouting-plaintext-multiroute-config
140 (let [s (get-service app :WebroutingService)
141 add-servlet-handler (partial add-servlet-handler s)
142 body "Hey there"
143 servlet (SimpleServlet. body)
144 svc (get-service app :TestDummy)]
145 (add-servlet-handler svc servlet {:route-id :quux})
146 (add-servlet-handler svc servlet {:route-id :foo})
147 (let [response (http-get "http://localhost:8080/foo")]
148 (is (= (:status response) 200))
149 (is (= (:body response) body)))
150 (let [response (http-get "http://localhost:8080/bar")]
151 (is (= (:status response) 200))
152 (is (= (:body response) body)))))))
153
154 (deftest war-test-web-routing
155 (testing "WAR support with web routing"
156 (with-app-with-config app
157 [jetty9-service
158 webrouting-service
159 test-dummy]
160 webrouting-plaintext-config
161 (let [s (get-service app :WebroutingService)
162 add-war-handler (partial add-war-handler s)
163 war "helloWorld.war"
164 svc (get-service app :TestDummy)]
165 (add-war-handler svc (str dev-resources-dir war))
166 (let [response (http-get "http://localhost:8080/foo/hello")]
167 (is (= (:status response) 200))
168 (is (= (:body response)
169 "<html>\n<head><title>Hello World Servlet</title></head>\n<body>Hello World!!</body>\n</html>\n"))))))
170
171 (testing "WAR support with multiple web routes"
172 (with-app-with-config app
173 [jetty9-service
174 webrouting-service
175 test-dummy]
176 webrouting-plaintext-multiroute-config
177 (let [s (get-service app :WebroutingService)
178 add-war-handler (partial add-war-handler s)
179 war "helloWorld.war"
180 svc (get-service app :TestDummy)]
181 (add-war-handler svc (str dev-resources-dir war) {:route-id :quux})
182 (add-war-handler svc (str dev-resources-dir war) {:route-id :foo})
183 (let [response (http-get "http://localhost:8080/foo/hello")]
184 (is (= (:status response) 200))
185 (is (= (:body response)
186 "<html>\n<head><title>Hello World Servlet</title></head>\n<body>Hello World!!</body>\n</html>\n")))
187 (let [response (http-get "http://localhost:8080/bar/hello")]
188 (is (= (:status response) 200))
189 (is (= (:body response)
190 "<html>\n<head><title>Hello World Servlet</title></head>\n<body>Hello World!!</body>\n</html>\n")))))))
191
192 (deftest endpoints-test-web-routing
193 (testing (str "get-registered-endpoints and log-registered-endpoints are "
194 "successful with the web-routing service")
195 (with-test-logging
196 (with-app-with-config app
197 [jetty9-service
198 webrouting-service
199 test-dummy]
200 webrouting-plaintext-config
201 (let [s (get-service app :WebroutingService)
202 get-registered-endpoints (partial get-registered-endpoints s)
203 log-registered-endpoints (partial log-registered-endpoints s)
204 add-ring-handler (partial add-ring-handler s)
205 ring-handler (fn [req] {:status 200 :body "Hi world"})
206 svc (get-service app :TestDummy)]
207 (add-ring-handler svc ring-handler)
208 (let [endpoints (get-registered-endpoints)]
209 (is (= endpoints {"/foo" [{:type :ring}]})))
210 (log-registered-endpoints)
211 (is (logged? #"^\{\"\/foo\" \[\{:type :ring}\]\}$"))
212 (is (logged? #"^\{\"\/foo\" \[\{:type :ring}\]\}$" :info)))))))
213
214
0 (ns puppetlabs.trapperkeeper.services.webrouting.webrouting-service-override-settings-test
1 (:require [clojure.test :refer :all]
2 [puppetlabs.trapperkeeper.services :as tk-services]
3 [puppetlabs.trapperkeeper.services.webrouting.webrouting-service :refer :all]
4 [puppetlabs.trapperkeeper.services.webserver.jetty9-service :refer [jetty9-service]]
5 [puppetlabs.trapperkeeper.app :refer [get-service]]
6 [puppetlabs.trapperkeeper.testutils.webrouting.common :refer :all]
7 [puppetlabs.trapperkeeper.testutils.bootstrap :refer [with-app-with-config]]
8 [puppetlabs.trapperkeeper.testutils.logging
9 :refer [with-test-logging]]
10 [schema.test :as schema-test]
11 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]))
12
13 (use-fixtures :once
14 schema-test/validate-schemas
15 testutils/assert-clean-shutdown)
16
17 (def dev-resources-dir "./dev-resources/")
18
19 (def dev-resources-config-dir (str dev-resources-dir "config/jetty/"))
20
21 (defprotocol TestDummy
22 (dummy [this]))
23
24 (tk-services/defservice test-dummy
25 TestDummy
26 []
27 (dummy [this]
28 "This is a dummy function. Please ignore."))
29
30 (def webrouting-plaintext-override-config
31 {:webserver {:port 8080}
32 :web-router-service
33 {:puppetlabs.trapperkeeper.services.webrouting.webrouting-service-override-settings-test/test-dummy "/foo"}})
34
35 (deftest test-override-webserver-settings!-with-web-routing
36 (let [ssl-port 9001
37 overrides {:ssl-port ssl-port
38 :ssl-host "0.0.0.0"
39 :ssl-cert
40 (str dev-resources-config-dir
41 "ssl/certs/localhost.pem")
42 :ssl-key
43 (str dev-resources-config-dir
44 "ssl/private_keys/localhost.pem")
45 :ssl-ca-cert
46 (str dev-resources-config-dir
47 "ssl/certs/ca.pem")
48 :ssl-crl-path
49 (str dev-resources-config-dir
50 "ssl/crls/crls_none_revoked.pem")}]
51 (testing "config override of all SSL settings before webserver starts is
52 successful with web-routing"
53 (let [override-result (atom nil)
54 service1 (tk-services/service
55 [[:WebroutingService override-webserver-settings!]]
56 (init [this context]
57 (reset! override-result
58 (override-webserver-settings!
59 overrides))
60 context))]
61 (with-test-logging
62 (with-app-with-config
63 app
64 [jetty9-service webrouting-service service1 test-dummy]
65 webrouting-plaintext-override-config
66 (let [s (get-service app :WebroutingService)
67 add-ring-handler (partial add-ring-handler s)
68 body "Hi World"
69 path "/foo"
70 ring-handler (fn [req] {:status 200 :body body})
71 svc (get-service app :TestDummy)]
72 (add-ring-handler svc ring-handler)
73 (let [response (http-get
74 (format "https://localhost:%d%s/" ssl-port path)
75 default-options-for-https-client)]
76 (is (= (:status response) 200)
77 "Unsuccessful http response code ring handler response.")
78 (is (= (:body response) body)
79 "Unexpected body in ring handler response."))))
80 (is (logged? #"^webserver config overridden for key 'ssl-port'")
81 "Didn't find log message for override of 'ssl-port'")
82 (is (logged? #"^webserver config overridden for key 'ssl-host'")
83 "Didn't find log message for override of 'ssl-host'")
84 (is (logged? #"^webserver config overridden for key 'ssl-cert'")
85 "Didn't find log message for override of 'ssl-cert'")
86 (is (logged? #"^webserver config overridden for key 'ssl-key'")
87 "Didn't find log message for override of 'ssl-key'")
88 (is (logged? #"^webserver config overridden for key 'ssl-ca-cert'")
89 "Didn't find log message for override of 'ssl-ca-cert'")
90 (is (logged? #"^webserver config overridden for key 'ssl-crl-path'")
91 "Didn't find log message for override of 'ssl-crl-path'"))
92 (is (= overrides @override-result)
93 "Unexpected response to override-webserver-settings! call.")))))
0 (ns puppetlabs.trapperkeeper.services.webrouting.webrouting-service-proxy-test
1 (:require [clojure.test :refer :all]
2 [puppetlabs.trapperkeeper.services.webrouting.webrouting-service :refer :all]
3 [puppetlabs.trapperkeeper.services.webserver.jetty9-service :refer [jetty9-service]]
4 [puppetlabs.trapperkeeper.testutils.webrouting.common :refer :all]
5 [puppetlabs.trapperkeeper.app :refer [get-service]]
6 [puppetlabs.trapperkeeper.testutils.bootstrap :refer [with-app-with-config]]
7 [puppetlabs.trapperkeeper.services :as tk-services]
8 [schema.test :as schema-test]
9 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]))
10
11 (use-fixtures :once
12 schema-test/validate-schemas
13 testutils/assert-clean-shutdown)
14
15 (defprotocol DummyService1
16 (dummy1 [this]))
17
18 (defprotocol DummyService2
19 (dummy2 [this]))
20
21 (tk-services/defservice dummy-service1
22 DummyService1
23 []
24 (dummy1 [this]
25 "This is a dummy function. Please ignore."))
26
27 (tk-services/defservice dummy-service2
28 DummyService2
29 []
30 (dummy2 [this]
31 "This is a dummy function. Please ignore."))
32
33 (defmacro with-target-and-proxy-servers
34 [{:keys [target proxy proxy-config proxy-opts]} & body]
35 `(with-app-with-config proxy-target-app#
36 [jetty9-service
37 webrouting-service
38 dummy-service1]
39 {:webserver ~target
40 :web-router-service {:puppetlabs.trapperkeeper.services.webrouting.webrouting-service-proxy-test/dummy-service1 "/hello"}}
41 (let [target-webserver# (get-service proxy-target-app# :WebroutingService)
42 svc# (get-service proxy-target-app# :DummyService1)]
43 (add-ring-handler
44 target-webserver#
45 svc#
46 (fn [req#]
47 (if (= "/hello/world" (:uri req#))
48 {:status 200 :body (str "Hello, World!"
49 ((:headers req#) "x-fancy-proxy-header"))}
50 {:status 404 :body "D'oh"}))))
51 (with-app-with-config proxy-app#
52 [jetty9-service
53 webrouting-service
54 dummy-service2]
55 {:webserver ~proxy
56 :web-router-service {:puppetlabs.trapperkeeper.services.webrouting.webrouting-service-proxy-test/dummy-service2
57 {:bar "/hello-proxy"
58 :foo "/goodbye-proxy"}}}
59 (let [proxy-webserver# (get-service proxy-app# :WebroutingService)
60 svc# (get-service proxy-app# :DummyService2)]
61 (if ~proxy-opts
62 (add-proxy-route proxy-webserver# svc# ~proxy-config ~proxy-opts)
63 (add-proxy-route proxy-webserver# svc# ~proxy-config {:route-id :bar})))
64 ~@body)))
65
66 (deftest proxy-test-web-routing
67 (testing "proxy support with web routing"
68 (with-target-and-proxy-servers
69 {:target {:host "0.0.0.0"
70 :port 9000}
71 :proxy {:host "0.0.0.0"
72 :port 10000}
73 :proxy-config {:host "localhost"
74 :port 9000
75 :path "/hello"}}
76 (let [response (http-get "http://localhost:9000/hello/world")]
77 (is (= (:status response) 200))
78 (is (= (:body response) "Hello, World!")))
79 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
80 (is (= (:status response) 200))
81 (is (= (:body response) "Hello, World!")))))
82
83 (testing "basic https proxy support with multiple web routes"
84 (with-target-and-proxy-servers
85 {:target {:host "0.0.0.0"
86 :port 9000}
87 :proxy {:host "0.0.0.0"
88 :port 10000}
89 :proxy-config {:host "localhost"
90 :port 9000
91 :path "/hello"}
92 :proxy-opts {:route-id :foo}}
93 (let [response (http-get "http://localhost:9000/hello/world")]
94 (is (= (:status response) 200))
95 (is (= (:body response) "Hello, World!")))
96 (let [response (http-get "http://localhost:10000/goodbye-proxy/world")]
97 (is (= (:status response) 200))
98 (is (= (:body response) "Hello, World!"))))))
0 (ns puppetlabs.trapperkeeper.services.webrouting.webrouting-service-test
1 (:require [clojure.test :refer :all]
2 [clojure.tools.logging :as log]
3 [gniazdo.core :as ws-client]
4 [puppetlabs.experimental.websockets.client :as ws-session]
5 [puppetlabs.kitchensink.testutils.fixtures :as ks-test-fixtures]
6 [puppetlabs.trapperkeeper.app :as tk-app]
7 [puppetlabs.trapperkeeper.core :as tk-core]
8 [puppetlabs.trapperkeeper.services :as tk-services]
9 [puppetlabs.trapperkeeper.services.webrouting.webrouting-service
10 :refer :all]
11 [puppetlabs.trapperkeeper.services.webserver.jetty9-service
12 :refer [jetty9-service]]
13 [puppetlabs.trapperkeeper.testutils.webrouting.common :refer :all]
14 [puppetlabs.trapperkeeper.testutils.bootstrap
15 :refer [with-app-with-empty-config
16 with-app-with-config]]
17 [puppetlabs.trapperkeeper.testutils.logging
18 :refer [with-test-logging]]
19 [schema.core :as schema]
20 [schema.test :as schema-test]
21 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]))
22
23 (use-fixtures :once
24 ks-test-fixtures/with-no-jvm-shutdown-hooks
25 schema-test/validate-schemas
26 testutils/assert-clean-shutdown)
27
28 (defprotocol TestService
29 (hello [this]))
30
31 (defprotocol TestService2)
32
33 (defprotocol TestService3)
34
35 (defprotocol NotReal
36 (dummy [this]))
37
38 (tk-services/defservice test-service
39 TestService
40 [[:WebroutingService add-ring-handler]]
41 (init [this context]
42 (let [body "Hello World!"
43 ring-handler (fn [req] {:status 200 :body body})]
44 (add-ring-handler this ring-handler {:route-id :bert})
45 (add-ring-handler this ring-handler {:route-id :bar})
46 (add-ring-handler this ring-handler {:route-id :baz})
47 (add-ring-handler this ring-handler {:route-id :quux}))
48 context)
49 (hello [this]
50 "This is a dummy function. Please disregard."))
51
52 (tk-services/defservice test-service-2
53 TestService2
54 [[:WebroutingService add-ring-handler]])
55
56 (tk-services/defservice test-service-3
57 TestService3
58 [[:WebroutingService add-ring-handler]])
59
60 (tk-services/defservice test-websocket-service
61 [[:WebroutingService add-websocket-handler]]
62 (init [this context]
63 (log/info "setting up webrouting websockets")
64 (let [handlers {:on-connect (fn [ws] (ws-session/send! ws "heyo"))}]
65 (add-websocket-handler this handlers))
66 context))
67
68 (tk-services/defservice not-real
69 NotReal
70 []
71 (dummy [this]
72 "This is a dummy function. Please disregard."))
73
74 (def webrouting-plaintext-multiserver-multiroute-config
75 {:webserver {:bar {:port 8080
76 :default-server true}
77 :foo {:port 9000}}
78 :web-router-service
79 {:puppetlabs.trapperkeeper.services.webrouting.webrouting-service-test/test-service
80 {:bert "/foo"
81 :bar "/bar"
82 :baz {:route "/foo"
83 :server "foo"}
84 :quux {:route "/bar"
85 :server "foo"}}
86 :puppetlabs.trapperkeeper.services.webrouting.webrouting-service-test/test-service-2
87 "/foo"
88 :puppetlabs.trapperkeeper.services.webrouting.webrouting-service-test/test-service-3
89 {:route "/foo" :server "foo"}
90 :puppetlabs.trapperkeeper.services.webrouting.webrouting-service-test/test-websocket-service
91 "/baz"}})
92
93 (def no-default-config
94 {:webserver {:bar {:port 8080}
95 :foo {:port 9000}}
96 :web-router-service
97 {:puppetlabs.trapperkeeper.services.webrouting.webrouting-service-test/test-service-2
98 "/foo"}})
99
100 (def default-route-config
101 {:webserver {:port 8080}
102 :web-router-service
103 {:puppetlabs.trapperkeeper.services.webrouting.webrouting-service-test/test-service-2
104 {:default "/foo"
105 :bar "/bar"}}})
106
107 (deftest webrouting-service-test
108 (testing "Other services can successfully use webrouting service"
109 (with-app-with-config
110 app
111 [jetty9-service webrouting-service test-service test-websocket-service]
112 webrouting-plaintext-multiserver-multiroute-config
113 (let [response (http-get "http://localhost:8080/foo/")]
114 (is (= (:status response) 200))
115 (is (= (:body response) "Hello World!")))
116 (let [response (http-get "http://localhost:8080/bar/")]
117 (is (= (:status response) 200))
118 (is (= (:body response) "Hello World!")))
119 (let [response (http-get "http://localhost:9000/foo/")]
120 (is (= (:status response) 200))
121 (is (= (:body response) "Hello World!")))
122 (let [response (http-get "http://localhost:9000/bar/")]
123 (is (= (:status response) 200))
124 (is (= (:body response) "Hello World!")))
125 (let [message (promise)
126 websocket (ws-client/connect "ws://localhost:8080/baz"
127 :on-receive (fn [text] (deliver message text)))]
128 (is (= @message "heyo"))
129 (ws-client/close websocket))))
130
131 (testing "Error occurs when specifying service that does not exist in config file"
132 (with-app-with-config
133 app
134 [jetty9-service webrouting-service not-real]
135 webrouting-plaintext-config
136 (let [s (tk-app/get-service app :WebroutingService)
137 add-ring-handler (partial add-ring-handler s)
138 body "Hello World!"
139 ring-handler (fn [req] {:status 200 :body body})
140 svc (tk-app/get-service app :NotReal)]
141 (is (thrown? IllegalArgumentException (add-ring-handler svc ring-handler))))))
142
143 (testing "Error occurs when endpoints don't have servers and no default is set"
144 (with-app-with-config
145 app
146 [jetty9-service webrouting-service test-service-2]
147 no-default-config
148 (let [s (tk-app/get-service app :WebroutingService)
149 add-ring-handler (partial add-ring-handler s)
150 body "Hello World!"
151 ring-handler (fn [req] {:status 200 :body body})
152 svc (tk-app/get-service app :TestService2)]
153 (is (thrown? IllegalArgumentException (add-ring-handler svc ring-handler))))))
154
155 (testing "Error occurs when not specifying a route-id for a multi-route config"
156 (with-app-with-config
157 app
158 [jetty9-service webrouting-service test-service-2]
159 default-route-config
160 (let [s (tk-app/get-service app :WebroutingService)
161 svc (tk-app/get-service app :TestService2)
162 add-ring-handler (partial add-ring-handler s)
163 ring-handler (fn [req] {:status 200 :body ""})]
164 (is (thrown? IllegalArgumentException (add-ring-handler svc ring-handler))))))
165
166 (testing "Can access route-ids for a service"
167 (with-app-with-config
168 app
169 [jetty9-service webrouting-service test-service test-service-2]
170 webrouting-plaintext-multiserver-multiroute-config
171 (let [s (tk-app/get-service app :WebroutingService)
172 svc (tk-app/get-service app :TestService)
173 svc2 (tk-app/get-service app :TestService2)
174 get-route (partial get-route s)]
175 (is (= "/foo" (get-route svc :bert)))
176 (is (= "/bar" (get-route svc :bar)))
177 (is (= "/foo" (get-route svc :baz)))
178 (is (= "/bar" (get-route svc :quux)))
179 (is (= "/foo" (get-route svc2))))))
180
181 (testing "Can access server for a service"
182 (with-app-with-config
183 app
184 [jetty9-service webrouting-service test-service test-service-2 test-service-3]
185 webrouting-plaintext-multiserver-multiroute-config
186 (let [s (tk-app/get-service app :WebroutingService)
187 svc (tk-app/get-service app :TestService)
188 svc2 (tk-app/get-service app :TestService2)
189 svc3 (tk-app/get-service app :TestService3)
190 get-server (partial get-server s)]
191 (is (= "foo" (get-server svc :baz)))
192 (is (= nil (get-server svc :bar)))
193 (is (= nil (get-server svc2)))
194 (is (= "foo" (get-server svc3)))))))
0 (ns puppetlabs.trapperkeeper.services.webserver.jetty9-config-test
1 (:import (clojure.lang ExceptionInfo)
2 (java.util Arrays))
3 (:require [clojure.test :refer :all]
4 [clojure.java.io :refer [resource]]
5 [me.raynes.fs :as fs]
6 [puppetlabs.ssl-utils.core :as ssl]
7 [puppetlabs.kitchensink.core :as ks]
8 [puppetlabs.trapperkeeper.services.webserver.jetty9-config :refer :all]
9 [puppetlabs.trapperkeeper.testutils.logging :refer [with-test-logging]]
10 [puppetlabs.trapperkeeper.services.webserver.jetty9-core :as jetty9]
11 [puppetlabs.trapperkeeper.services.webserver.jetty9-service
12 :refer [jetty9-service add-ring-handler]]
13 [puppetlabs.trapperkeeper.app :as tk-app]
14 [puppetlabs.trapperkeeper.testutils.bootstrap :refer [with-app-with-config]]
15 [puppetlabs.trapperkeeper.testutils.webserver.common :refer [http-get]]
16 [schema.test :as schema-test]
17 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]))
18
19 (use-fixtures :once
20 schema-test/validate-schemas
21 testutils/assert-clean-shutdown)
22
23 (def valid-ssl-pem-config
24 {:ssl-cert "./dev-resources/config/jetty/ssl/certs/localhost.pem"
25 :ssl-key "./dev-resources/config/jetty/ssl/private_keys/localhost.pem"
26 :ssl-ca-cert "./dev-resources/config/jetty/ssl/certs/ca.pem"})
27
28 (def valid-ssl-keystore-config
29 {:keystore "./dev-resources/config/jetty/ssl/keystore.jks"
30 :truststore "./dev-resources/config/jetty/ssl/truststore.jks"
31 :key-password "Kq8lG9LkISky9cDIYysiadxRx"
32 :trust-password "Kq8lG9LkISky9cDIYysiadxRx"})
33
34 (defn munge-actual-http-config
35 [config]
36 (process-config config))
37
38 (defn munge-expected-common-config
39 [expected scheme]
40 (-> expected
41 (update-in [:max-threads] identity)
42 (update-in [:queue-max-size] identity)
43 (update-in [:jmx-enable] (fnil ks/parse-bool default-jmx-enable))
44 (update-in [scheme :request-header-max-size] identity)
45 (update-in [scheme :so-linger-milliseconds] identity)
46 (update-in [scheme :idle-timeout-milliseconds] identity)
47 (update-in [scheme :acceptor-threads] identity)
48 (update-in [scheme :selector-threads] identity)))
49
50 (defn munge-expected-http-config
51 [expected]
52 (munge-expected-common-config expected :http))
53
54 (defn munge-actual-https-config
55 [config]
56 (let [actual (process-config config)]
57 (-> actual
58 (update-in [:https] dissoc :keystore-config))))
59
60 (defn munge-expected-https-config
61 [expected]
62 (-> (munge-expected-common-config expected :https)
63 (update-in [:https :cipher-suites] (fnil identity acceptable-ciphers))
64 (update-in [:https :protocols] (fnil identity default-protocols))
65 (update-in [:https :client-auth] (fnil identity default-client-auth))
66 (update-in [:https :ssl-crl-path] identity)))
67
68 (deftest process-config-http-test
69 (testing "process-config successfully builds a WebserverConfig for plaintext connector"
70 (is (= (munge-actual-http-config
71 {:port 8000})
72 (munge-expected-http-config
73 {:http {:host default-host :port 8000}})))
74
75 (is (= (munge-actual-http-config
76 {:port 8000 :host "foo.local"})
77 (munge-expected-http-config
78 {:http {:host "foo.local" :port 8000}})))
79
80 (is (= (munge-actual-http-config
81 {:host "foo.local"})
82 (munge-expected-http-config
83 {:http {:host "foo.local" :port default-http-port}})))
84
85 (is (= (munge-actual-http-config
86 {:port 8000 :request-header-max-size 16192})
87 (munge-expected-http-config
88 {:http {:host default-host
89 :port 8000
90 :request-header-max-size 16192}})))
91
92 (is (= (munge-actual-http-config
93 {:port 8000 :so-linger-seconds 7})
94 (munge-expected-http-config
95 {:http {:host default-host
96 :port 8000
97 :so-linger-milliseconds 7000}})))
98
99 (is (= (munge-actual-http-config
100 {:port 8000 :max-threads 500})
101 (munge-expected-http-config
102 {:http {:host default-host :port 8000}
103 :max-threads 500})))
104
105 (is (= (munge-actual-http-config
106 {:port 8000 :queue-max-size 123})
107 (munge-expected-http-config
108 {:http {:host default-host :port 8000}
109 :queue-max-size 123})))
110
111 (is (= (munge-actual-http-config
112 {:port 8000 :idle-timeout-milliseconds 6000})
113 (munge-expected-http-config
114 {:http {:host default-host
115 :port 8000
116 :idle-timeout-milliseconds 6000}})))
117
118 (is (= (munge-actual-http-config
119 {:port 8000 :acceptor-threads 32})
120 (munge-expected-http-config
121 {:http {:host default-host
122 :port 8000
123 :acceptor-threads 32}})))
124
125 (is (= (munge-actual-http-config
126 {:port 8000 :selector-threads 52})
127 (munge-expected-http-config
128 {:http {:host default-host
129 :port 8000
130 :selector-threads 52}})))))
131
132 (deftest process-config-https-test
133 (testing "process-config successfully builds a WebserverConfig for ssl connector"
134 (is (= (munge-actual-https-config
135 (merge valid-ssl-pem-config
136 {:ssl-host "foo.local"}))
137 (munge-expected-https-config
138 {:https {:host "foo.local" :port default-https-port}})))
139
140 (is (= (munge-actual-https-config
141 (merge valid-ssl-pem-config
142 {:ssl-port 8001}))
143 (munge-expected-https-config
144 {:https {:host default-host :port 8001}})))
145
146 (is (= (munge-actual-https-config
147 (merge valid-ssl-pem-config
148 {:ssl-host "foo.local" :ssl-port 8001}))
149 (munge-expected-https-config
150 {:https {:host "foo.local" :port 8001}})))
151
152 (is (= (munge-actual-https-config
153 (merge valid-ssl-pem-config
154 {:ssl-host "foo.local"
155 :ssl-port 8001
156 :request-header-max-size 16192}))
157 (munge-expected-https-config
158 {:https {:host "foo.local"
159 :port 8001
160 :request-header-max-size 16192}})))
161
162 (is (= (munge-actual-https-config
163 (merge valid-ssl-pem-config
164 {:ssl-host "foo.local"
165 :ssl-port 8001
166 :so-linger-seconds 22}))
167 (munge-expected-https-config
168 {:https {:host "foo.local"
169 :port 8001
170 :so-linger-milliseconds 22000}})))
171
172 (is (= (munge-actual-https-config
173 (merge valid-ssl-pem-config
174 {:ssl-host "foo.local"
175 :ssl-port 8001
176 :max-threads 93}))
177 (munge-expected-https-config
178 {:https {:host "foo.local" :port 8001}
179 :max-threads 93})))
180
181 (is (= (munge-actual-https-config
182 (merge valid-ssl-pem-config
183 {:ssl-host "foo.local"
184 :ssl-port 8001
185 :queue-max-size 99}))
186 (munge-expected-https-config
187 {:https {:host "foo.local" :port 8001}
188 :queue-max-size 99})))
189
190 (is (= (munge-actual-https-config
191 (merge valid-ssl-pem-config
192 {:ssl-host "foo.local"
193 :ssl-port 8001
194 :idle-timeout-milliseconds 4200}))
195 (munge-expected-https-config
196 {:https {:host "foo.local"
197 :port 8001
198 :idle-timeout-milliseconds 4200}})))
199
200 (is (= (munge-actual-https-config
201 (merge valid-ssl-pem-config
202 {:ssl-host "foo.local"
203 :ssl-port 8001
204 :ssl-selector-threads 4242}))
205 (munge-expected-https-config
206 {:https {:host "foo.local"
207 :port 8001
208 :selector-threads 4242}})))
209
210 (is (= (munge-actual-https-config
211 (merge valid-ssl-pem-config
212 {:ssl-host "foo.local"
213 :ssl-port 8001
214 :ssl-acceptor-threads 9193}))
215 (munge-expected-https-config
216 {:https {:host "foo.local"
217 :port 8001
218 :acceptor-threads 9193}})))))
219
220 (deftest process-config-jks-test
221 (testing "jks ssl config"
222 (is (= (munge-actual-https-config
223 (merge valid-ssl-keystore-config
224 {:ssl-port 8001}))
225 (munge-expected-https-config
226 {:https {:host default-host :port 8001}})))))
227
228 (deftest process-config-ciphers-test
229 (testing "cipher suites"
230 (is (= (munge-actual-https-config
231 (merge valid-ssl-pem-config
232 {:ssl-port 8001 :cipher-suites ["FOO" "BAR"]}))
233 (munge-expected-https-config
234 {:https
235 {:host default-host
236 :port 8001
237 :cipher-suites ["FOO" "BAR"]}}))))
238
239 (testing "cipher suites as a comma and space-separated string"
240 (is (= (munge-actual-https-config
241 (merge valid-ssl-pem-config
242 {:ssl-port 8001 :cipher-suites "FOO, BAR"}))
243 (munge-expected-https-config
244 {:https
245 {:host default-host
246 :port 8001
247 :cipher-suites ["FOO" "BAR"]}})))))
248
249 (deftest process-config-protocols-test
250 (testing "protocols"
251 (is (= (munge-actual-https-config
252 (merge valid-ssl-pem-config
253 {:ssl-port 8001 :ssl-protocols ["FOO" "BAR"]}))
254 (munge-expected-https-config
255 {:https
256 {:host default-host
257 :port 8001
258 :protocols ["FOO" "BAR"]}}))))
259
260 (testing "protocols as a comma and space-separated string"
261 (is (= (munge-actual-https-config
262 (merge valid-ssl-pem-config
263 {:ssl-port 8001 :ssl-protocols "FOO, BAR"}))
264 (munge-expected-https-config
265 {:https
266 {:host default-host
267 :port 8001
268 :protocols ["FOO" "BAR"]}})))))
269
270 (deftest process-config-crl-test
271 (testing "ssl-crl-path"
272 (is (= (munge-actual-https-config
273 (merge valid-ssl-pem-config
274 {:ssl-port 8001
275 :ssl-crl-path
276 "./dev-resources/config/jetty/ssl/certs/ca.pem"}))
277 (munge-expected-https-config
278 {:https
279 {:host default-host
280 :port 8001
281 :ssl-crl-path "./dev-resources/config/jetty/ssl/certs/ca.pem"}})))))
282
283 (deftest process-config-client-auth-test
284 (testing "client auth"
285 (letfn [(get-client-auth [config]
286 (-> config
287 (merge valid-ssl-pem-config)
288 process-config
289 (get-in [:https :client-auth])))]
290 (testing "configure-web-server should set client-auth to a value of :need
291 if not specified in options"
292 (is (= :need (get-client-auth {:ssl-port 8001}))))
293
294 (testing "configure-web-server should convert client-auth string to
295 appropriate corresponding keyword value in configure-web-server
296 options"
297 (is (= :need (get-client-auth {:ssl-port 8081 :client-auth "need"})))
298 (is (= :want (get-client-auth {:ssl-port 8081 :client-auth "want"})))
299 (is (= :none (get-client-auth {:ssl-port 8081 :client-auth "none"}))))
300
301 (testing "configure-web-server should throw IllegalArgumentException if an
302 unsupported value is specified for the client-auth option"
303 (is (thrown-with-msg? java.lang.IllegalArgumentException
304 #"Unexpected value found for client auth config option: bogus. Expected need, want, or none."
305 (get-client-auth {:ssl-port 8081 :client-auth "bogus"})))))))
306
307 (deftest process-config-http-plus-https-test
308 (testing "process-config successfully builds a WebserverConfig for plaintext+ssl"
309 (is (= (munge-actual-https-config
310 (merge valid-ssl-pem-config
311 {:ssl-host "foo.local" :port 8000}))
312 (munge-expected-https-config
313 {:http {:host default-host
314 :port 8000
315 :request-header-max-size nil
316 :so-linger-milliseconds nil
317 :idle-timeout-milliseconds nil
318 :acceptor-threads nil
319 :selector-threads nil}
320 :https {:host "foo.local" :port default-https-port}})))))
321
322 (deftest process-config-invalid-test
323 (testing "process-config fails for invalid server config"
324 (are [config]
325 (thrown? ExceptionInfo
326 (process-config config))
327 {:port "foo"}
328 {:port 8000 :badkey "hi"}))
329
330 (testing "process-config fails for incomplete ssl context config"
331 (are [config]
332 (thrown? IllegalArgumentException
333 (process-config config))
334 {}
335 {:ssl-port 8001}
336 {:ssl-port 8001 :ssl-host "foo.local"}
337 {:ssl-host "foo.local"}
338 valid-ssl-pem-config
339 (merge {:ssl-port 8001} (dissoc valid-ssl-pem-config :ssl-key))
340 (merge {:ssl-port 8001} (dissoc valid-ssl-keystore-config :keystore))))
341
342 (testing "should warn if both keystore-based and PEM-based SSL settings are found"
343 (with-test-logging
344 (process-config (merge {:ssl-port 8001}
345 valid-ssl-pem-config
346 valid-ssl-keystore-config))
347 (is (logged? #"Found settings for both keystore-based and PEM-based SSL")))))
348
349 (defn- validate-cert-lists-equal
350 [pem-with-expected-certs ssl-cert ssl-cert-chain]
351 (let [expected-certs (ssl/pem->certs pem-with-expected-certs)
352 actual-certs (construct-ssl-x509-cert-chain ssl-cert ssl-cert-chain)]
353 (is (= (count expected-certs) (count actual-certs))
354 "Number of expected certs do not match number of actual certs")
355 (dotimes [n (count expected-certs)]
356 (is (Arrays/equals (.getEncoded (nth expected-certs n))
357 (.getEncoded (nth actual-certs n)))
358 (str "Expected cert # " n " from does not match actual cert")))))
359
360 (deftest construct-ssl-x509-cert-chain-test
361 (testing "non-existent ssl-cert throws expected exception"
362 (let [tmp-file (ks/temp-file)]
363 (fs/delete tmp-file)
364 (is (thrown-with-msg? IllegalArgumentException
365 #"^Unable to open 'ssl-cert' file:"
366 (construct-ssl-x509-cert-chain
367 (.getAbsolutePath tmp-file)
368 nil)))))
369
370 (testing "no content in ssl-cert throws expected exception"
371 (let [tmp-file (ks/temp-file)]
372 (is (thrown-with-msg? Exception
373 #"^No certs found in 'ssl-cert' file:"
374 (construct-ssl-x509-cert-chain
375 (.getAbsolutePath tmp-file)
376 nil)))))
377
378 (testing "non-existent ssl-cert-chain throws expected exception"
379 (let [tmp-file (ks/temp-file)]
380 (fs/delete tmp-file)
381 (is (thrown-with-msg? IllegalArgumentException
382 #"^Unable to open 'ssl-cert-chain' file:"
383 (construct-ssl-x509-cert-chain
384 "./dev-resources/config/jetty/ssl/certs/localhost.pem"
385 (.getAbsolutePath tmp-file))))))
386
387 (testing "ssl-cert with single cert loaded into list"
388 (validate-cert-lists-equal
389 "./dev-resources/config/jetty/ssl/certs/localhost.pem"
390 "./dev-resources/config/jetty/ssl/certs/localhost.pem"
391 nil))
392
393 (testing "ssl-cert with multiple certs loaded into list"
394 (validate-cert-lists-equal
395 "./dev-resources/config/jetty/ssl/certs/master-with-all-cas.pem"
396 "./dev-resources/config/jetty/ssl/certs/master-with-all-cas.pem"
397 nil))
398
399 (testing (str "ssl-cert with single cert and ssl-cert-chain with "
400 "multiple certs loaded into list")
401 (validate-cert-lists-equal
402 "./dev-resources/config/jetty/ssl/certs/master-with-all-cas.pem"
403 "./dev-resources/config/jetty/ssl/certs/master.pem"
404 "./dev-resources/config/jetty/ssl/certs/ca-master-intermediate-and-root.pem"))
405
406 (testing (str "for ssl-cert with multiple certs and ssl-cert-chain with "
407 "with one cert, only the first cert from ssl-cert is "
408 "loaded into list with cert from ssl-cert-chain")
409 (validate-cert-lists-equal
410 "./dev-resources/config/jetty/ssl/certs/master-with-root-ca.pem"
411 "./dev-resources/config/jetty/ssl/certs/master-with-intermediate-ca.pem"
412 "./dev-resources/config/jetty/ssl/certs/ca-root.pem")))
413
414 (deftest test-advanced-scripting-config
415 (testing "Verify that we can use scripting to handle advanced configuration scenarios"
416 (let [config {:webserver
417 {:port 9000
418 :host "localhost"
419 :post-config-script (str "import org.eclipse.jetty.server.ServerConnector;"
420 "ServerConnector c = (ServerConnector)(server.getConnectors()[0]);\n"
421 "c.setPort(10000);")}}]
422 (with-test-logging
423 (with-app-with-config app
424 [jetty9-service]
425 config
426 (let [s (tk-app/get-service app :WebserverService)
427 add-ring-handler (partial add-ring-handler s)
428 body "Hi World"
429 path "/hi_world"
430 ring-handler (fn [req] {:status 200 :body body})]
431 (testing "A warning is logged when using post-config-script"
432 (is (logged? #"The 'post-config-script' setting is for advanced use"
433 :warn)))
434
435 (testing "scripted changes are executed properly"
436 (add-ring-handler ring-handler path)
437 (let [response (http-get
438 (format "http://localhost:10000/%s" path))]
439 (is (= (:status response) 200))
440 (is (= (:body response) body)))))))))
441
442 (testing "Server fails to start with bad post-config-script"
443 (let [base-config {:port 9000
444 :host "localhost"}]
445 (testing "Throws an error if the script can't be compiled."
446 (is (thrown-with-msg?
447 IllegalArgumentException
448 #"Invalid script string in webserver 'post-config-script' configuration"
449 (let [context (jetty9/initialize-context)]
450 (with-test-logging
451 (try
452 (jetty9/start-webserver!
453 context
454 (merge base-config
455 {:post-config-script (str "AHAHHHGHAHAHAHEASD! OMG!")}))
456 (finally
457 (jetty9/shutdown context))))))))
458 (testing "Throws an error if the script can't be executed."
459 (is (thrown-with-msg?
460 IllegalArgumentException
461 #"Invalid script string in webserver 'post-config-script' configuration"
462 (let [context (jetty9/initialize-context)]
463 (with-test-logging
464 (try
465 (jetty9/start-webserver!
466 context
467 (merge base-config
468 {:post-config-script (str "Object x = null; x.toString();")}))
469 (finally
470 (jetty9/shutdown context)))))))))))
0 (ns puppetlabs.trapperkeeper.services.webserver.jetty9-core-test
1 (:import
2 (org.eclipse.jetty.server.handler ContextHandlerCollection)
3 (java.security KeyStore)
4 (java.net SocketTimeoutException Socket)
5 (java.io InputStreamReader BufferedReader PrintWriter)
6 (org.eclipse.jetty.server Server ServerConnector)
7 (appender TestListAppender))
8 (:require [clojure.test :refer :all]
9 [clojure.java.jmx :as jmx]
10 [ring.util.response :as rr]
11 [puppetlabs.http.client.sync :as http-sync]
12 [puppetlabs.kitchensink.core :as ks]
13 [puppetlabs.trapperkeeper.services.webserver.jetty9-core :as jetty]
14 [puppetlabs.trapperkeeper.testutils.webserver
15 :refer [with-test-webserver with-test-webserver-and-config]]
16 [puppetlabs.trapperkeeper.app :as tk-app]
17 [puppetlabs.trapperkeeper.services.webserver.jetty9-default-config-test
18 :refer [get-server-thread-pool-queue]]
19 [puppetlabs.trapperkeeper.services.webserver.jetty9-service
20 :refer [jetty9-service add-ring-handler]]
21 [puppetlabs.trapperkeeper.testutils.logging :refer [with-test-logging]]
22 [puppetlabs.trapperkeeper.testutils.bootstrap :refer [with-app-with-config]]
23 [schema.test :as schema-test]
24 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]
25 [puppetlabs.trapperkeeper.testutils.logging :as tk-log-testutils]))
26
27 (use-fixtures :once
28 schema-test/validate-schemas
29 testutils/assert-clean-shutdown)
30
31 (deftest handlers
32 (testing "create-handlers should allow for handlers to be added"
33 (let [webserver-context (jetty/initialize-context)
34 handlers (:handlers webserver-context)]
35 (jetty/add-ring-handler webserver-context
36 (fn [req] {:status 200
37 :body "I am a handler"})
38 "/"
39 true
40 false)
41 (is (= (count (.getHandlers handlers)) 1)))))
42
43 (defn validate-gzip-encoding-when-gzip-requested
44 [body port]
45 ;; The client/get function asks for compression by default
46 (let [resp (http-sync/get (format "http://localhost:%d/" port))]
47 (is (= (slurp (resp :body)) body))
48 (is (= (get-in resp [:orig-content-encoding]) "gzip")
49 (format "Expected gzipped response, got this response: %s"
50 resp))))
51
52 (defn validate-no-gzip-encoding-when-gzip-not-requested
53 [body port]
54 ;; The client/get function asks for compression by default
55 (let [resp (http-sync/get (format "http://localhost:%d/" port)
56 {:decompress-body false})]
57 (is (= (slurp (resp :body)) body))
58 ;; We should not receive a content-encoding header in the
59 ;; uncompressed case
60 (is (nil? (get-in resp [:headers "content-encoding"]))
61 (format "Expected uncompressed response, got this response: %s"
62 resp))))
63
64 (defn validate-no-gzip-encoding-even-though-gzip-requested
65 [body port]
66 ;; The client/get function asks for compression by default
67 (let [resp (http-sync/get (format "http://localhost:%d/" port))]
68 (is (= (slurp (resp :body)) body))
69 ;; We should not receive a content-encoding header in the
70 ;; uncompressed case
71 (is (nil? (get-in resp [:headers "content-encoding"]))
72 (format "Expected uncompressed response, got this response: %s"
73 resp))))
74
75 (deftest compression
76 (testing "should return"
77 ;; Jetty may not Gzip encode a response body if the size of the response
78 ;; is less than 256 bytes, so returning a larger body to ensure that Gzip
79 ;; encoding is used where desired for these tests
80 (let [body (apply str (repeat 1000 "f"))
81 app (fn [req]
82 (-> body
83 (rr/response)
84 (rr/status 200)
85 (rr/content-type "text/plain")
86 (rr/charset "UTF-8")))]
87 (with-test-webserver app port
88 (testing "a gzipped response when request wants a compressed one and
89 server not configured with a default for gzip-enable"
90 (validate-gzip-encoding-when-gzip-requested body port))
91
92 (testing "an uncompressed response when request doesn't ask for a
93 compressed one and server not configured with a default for
94 gzip-enable"
95 (validate-no-gzip-encoding-when-gzip-not-requested body port)))
96
97 (with-test-webserver-and-config app port {:gzip-enable true}
98 (testing "a gzipped response when request wants a compressed one and
99 server configured with a true value for gzip-enable"
100 (validate-gzip-encoding-when-gzip-requested body port))
101
102 (testing "an uncompressed response when request doesn't ask for a
103 compressed one and server configured with a true value for
104 gzip-enable"
105 (validate-no-gzip-encoding-when-gzip-not-requested body port)))
106
107 (with-test-webserver-and-config app port {:gzip-enable false}
108 (testing "an uncompressed response when request wants a compressed one
109 but server configured with a false value for gzip-enable"
110 (validate-no-gzip-encoding-even-though-gzip-requested body port))
111
112 (testing "an uncompressed response when request doesn't ask for a
113 compressed one and server configured with a false value for
114 gzip-enable"
115 (validate-no-gzip-encoding-when-gzip-not-requested body port)))
116
117 (try
118 (with-test-webserver-and-config
119 app
120 port {:gzip-enable true
121 :access-log-config
122 (str "./dev-resources/puppetlabs/trapperkeeper/services/webserver/"
123 "request-logging.xml")}
124 (testing "(TK-429) a gzipped response when request wants a compressed one
125 and server configured with a true value for gzip-enable and an
126 access-log-config"
127 (validate-gzip-encoding-when-gzip-requested body port)))
128 (finally
129 (.clear (TestListAppender/list)))))))
130
131 (deftest jmx
132 (testing "by default Jetty JMX support is enabled"
133 (with-test-webserver #() _
134 (testing "and should return a valid Jetty MBeans object"
135 (let [mbeans (jmx/mbean-names "org.eclipse.jetty.jmx:*")]
136 (is (not (empty? mbeans)))))
137
138 (testing "and should not return data when we query for something unexpected"
139 (let [mbeans (jmx/mbean-names "foobarbaz:*")]
140 (is (empty? mbeans))))))
141
142 (testing "server starts up and shuts down cleanly when jmx is disabled"
143 (let [config {:webserver {:port 9000
144 :host "localhost"
145 :jmx-enable "false"}}]
146 (with-app-with-config app
147 [jetty9-service]
148 config))))
149
150 (deftest override-webserver-settings!-tests
151 (let [default-state {:mbean-container nil
152 :overrides-read-by-webserver false
153 :overrides nil
154 :endpoints {}
155 :ssl-context-factory nil}
156 webserver-context (fn [state]
157 {:handlers (ContextHandlerCollection.)
158 :server nil
159 :state (atom (merge default-state state))})]
160 (testing "able to associate overrides when overrides not already set"
161 (let [context (webserver-context
162 {})]
163 (is (= {:host "override-value-1"
164 :ssl-host "override-value-2"}
165 (jetty/override-webserver-settings!
166 context
167 {:host "override-value-1"
168 :ssl-host "override-value-2"}))
169 "Unexpected overrides returned from override-webserver-settings!")
170 (is (= @(:state context)
171 (merge default-state
172 {:overrides {:host "override-value-1"
173 :ssl-host "override-value-2"}}))
174 "Unexpected config set for override-webserver-settings!")))
175 (testing "unable to associate overrides when overrides already processed by
176 webserver but overrides were not present"
177 (let [context (webserver-context
178 {:overrides-read-by-webserver true})]
179 (is (thrown-with-msg? java.lang.IllegalStateException
180 #"overrides cannot be set because webserver has already processed the config"
181 (jetty/override-webserver-settings!
182 context
183 {:host "override-value-1"
184 :ssl-host "override-value-2"}))
185 "Call to override-webserver-settings! did not fail as expected.")
186 (is (= (merge default-state {:overrides-read-by-webserver true})
187 @(:state context))
188 "Config unexpectedly changed for override-webserver-settings!")))
189 (testing "unable to associate override when overrides already processed by
190 webserver and overrides were previously set"
191 (let [context (webserver-context
192 {:overrides {:myoverride "my-override-value"}
193 :overrides-read-by-webserver true})]
194 (is (thrown-with-msg? java.lang.IllegalStateException
195 #"overrides cannot be set because they have already been set and webserver has already processed the config"
196 (jetty/override-webserver-settings!
197 context
198 {:host "override-value-1"
199 :ssl-host "override-value-2"}))
200 "Call to override-webserver-settings! did not fail as expected.")
201 (is (= (merge default-state
202 {:overrides {:myoverride "my-override-value"}
203 :overrides-read-by-webserver true})
204 @(:state context))
205 "Config unexpectedly changed for override-webserver-settings!")))
206 (testing "unable to associate override when overrides were previously set"
207 (let [context (webserver-context
208 {:overrides {:myoverride "my-override-value"}})]
209 (is (thrown-with-msg? java.lang.IllegalStateException
210 #"overrides cannot be set because they have already been set"
211 (jetty/override-webserver-settings!
212 context
213 {:host "override-value-1"
214 :ssl-host "override-value-2"}))
215 "Call to override-webserver-settings! did not fail as expected.")
216 (is (= (merge default-state
217 {:overrides {:myoverride "my-override-value"}})
218 @(:state context))
219 "config unexpectedly changed for override-webserver-settings!")))))
220
221
222 (defn munge-common-connector-config
223 [config connector-keyword]
224 (-> config
225 (update-in [connector-keyword :port] (fnil identity 0))
226 (update-in [connector-keyword :host] (fnil identity "localhost"))
227 (update-in [connector-keyword :request-header-max-size] identity)
228 (update-in [connector-keyword :acceptor-threads] identity)
229 (update-in [connector-keyword :selector-threads] identity)
230 (update-in [connector-keyword :so-linger-milliseconds] identity)
231 (update-in [connector-keyword :idle-timeout-milliseconds] identity)))
232
233 (defn munge-http-connector-config
234 [config]
235 (-> config
236 (update-in [:max-threads] identity)
237 (update-in [:queue-max-size] identity)
238 (update-in [:jmx-enable] ks/parse-bool)
239 (munge-common-connector-config :http)))
240
241 (defn munge-http-and-https-connector-config
242 [config]
243 (-> config
244 (munge-http-connector-config)
245 (munge-common-connector-config :https)
246 (update-in [:https :protocols] identity)
247 (update-in [:https :cipher-suites] identity)
248 (update-in [:https :client-auth] (fnil identity :none))
249 (update-in [:https :keystore-config]
250 (fnil identity
251 {:truststore (-> (KeyStore/getDefaultType)
252 (KeyStore/getInstance))
253 :key-password "hello"
254 :keystore (-> (KeyStore/getDefaultType)
255 (KeyStore/getInstance))}))))
256
257 (defn create-server-with-config
258 [config]
259 (jetty/create-server (jetty/initialize-context) config))
260
261 (defn create-server-with-partial-http-config
262 [config]
263 (create-server-with-config (munge-http-connector-config config)))
264
265 (defn create-server-with-partial-http-and-https-config
266 [config]
267 (create-server-with-config (munge-http-and-https-connector-config config)))
268
269 (defn get-thread-pool-for-partial-http-config
270 [config]
271 (.getThreadPool (create-server-with-partial-http-config config)))
272
273 (def get-thread-pool-for-default-server (.getThreadPool (Server.)))
274
275 (def default-server-max-threads (.getMaxThreads
276 get-thread-pool-for-default-server))
277
278 (defn get-max-threads-for-partial-http-config
279 [config]
280 (.getMaxThreads (get-thread-pool-for-partial-http-config config)))
281
282 (deftest create-server-max-threads-test
283 (testing "default max threads passed through to thread pool"
284 (is (= default-server-max-threads
285 (get-max-threads-for-partial-http-config {:max-threads nil}))))
286 (testing "custom max threads passed through to thread pool"
287 (is (= 9042
288 (get-max-threads-for-partial-http-config
289 {:max-threads 9042, :queue-max-size nil})))))
290
291 (deftest create-server-queue-max-size-test
292 (let [get-queue-for-partial-http-config (fn [config]
293 (get-server-thread-pool-queue
294 (create-server-with-config
295 (munge-http-connector-config
296 config))))
297 default-server-min-threads (.getMinThreads
298 get-thread-pool-for-default-server)]
299 (testing "default queue max size passed through to thread pool queue"
300 (is (= (.getMaxCapacity (get-server-thread-pool-queue (Server.)))
301 (.getMaxCapacity (get-queue-for-partial-http-config
302 {:queue-max-size nil})))))
303 (testing "custom default queue max size passed through to thread pool queue"
304 (is (= 393
305 (.getMaxCapacity (get-queue-for-partial-http-config
306 {:queue-max-size 393})))))
307 (testing (str "default max threads passed through to thread pool when "
308 "queue-max-size set")
309 (is (= default-server-max-threads
310 (get-max-threads-for-partial-http-config
311 {:max-threads nil, :queue-max-size 1}))))
312 (testing "min threads passed through to thread pool when queue-max-size set"
313 (is (= default-server-min-threads
314 (.getMinThreads (get-thread-pool-for-partial-http-config
315 {:queue-max-size 1})))))
316 (testing "idle timeout passed through to thread pool when queue-max-size set"
317 (is (= (.getIdleTimeout get-thread-pool-for-default-server)
318 (.getIdleTimeout (get-thread-pool-for-partial-http-config
319 {:queue-max-size 1})))))
320 (testing (str "queue min size set on thread pool queue equal to min threads "
321 "when queue max size greater than min threads")
322 (is (= default-server-min-threads
323 (.getCapacity (get-queue-for-partial-http-config
324 {:queue-max-size
325 (inc default-server-min-threads)})))))
326 (testing (str "queue min size set on thread pool queue equal to queue max "
327 "size when queue max size less than min threads")
328 (let [queue-max-size (dec default-server-min-threads)]
329 (is (= queue-max-size
330 (.getCapacity (get-queue-for-partial-http-config
331 {:queue-max-size queue-max-size}))))))))
332
333 (deftest create-server-so-linger-test
334 (testing "so-linger-time configured properly for http connector"
335 (let [server (create-server-with-partial-http-config
336 {:http {:so-linger-milliseconds 500}})
337 connectors (.getConnectors server)]
338 (is (= 1 (count connectors))
339 "Unexpected number of connectors for server")
340 (is (= 500 (.getSoLingerTime (first connectors)))
341 "Unexpected so linger time for connector")))
342 (testing "so-linger-time configured properly for multiple connectors"
343 (let [server (create-server-with-partial-http-and-https-config
344 {:http {:port 25
345 :so-linger-milliseconds 41}
346 :https {:port 92
347 :so-linger-milliseconds 42}})
348 connectors (.getConnectors server)]
349 (is (= 2 (count connectors))
350 "Unexpected number of connectors for server")
351 (is (= 25 (.getPort (first connectors)))
352 "Unexpected port for first connector")
353 (is (= 41 (.getSoLingerTime (first connectors)))
354 "Unexpected so linger time for first connector")
355 (is (= 92 (.getPort (second connectors)))
356 "Unexpected port for second connector")
357 (is (= 42 (.getSoLingerTime (second connectors)))
358 "Unexpected so linger time for second connector"))))
359
360 (deftest create-server-idle-timeout-test
361 (testing "idle-timeout configured properly for http connector"
362 (let [server (create-server-with-partial-http-config
363 {:http {:idle-timeout-milliseconds 3000}})
364 connectors (.getConnectors server)]
365 (is (= 1 (count connectors))
366 "Unexpected number of connectors for server")
367 (is (= 3000 (.getIdleTimeout (first connectors)))
368 "Unexpected idle time for connector")))
369 (testing "idle-timeout configured properly for multiple connectors"
370 (let [server (create-server-with-partial-http-and-https-config
371 {:http {:port 25
372 :idle-timeout-milliseconds 9001}
373 :https {:port 92
374 :idle-timeout-milliseconds 9002}})
375 connectors (.getConnectors server)]
376 (is (= 2 (count connectors))
377 "Unexpected number of connectors for server")
378 (is (= 25 (.getPort (first connectors)))
379 "Unexpected port for first connector")
380 (is (= 9001 (.getIdleTimeout (first connectors)))
381 "Unexpected idle timeout for first connector")
382 (is (= 92 (.getPort (second connectors)))
383 "Unexpected port for second connector")
384 (is (= 9002 (.getIdleTimeout (second connectors)))
385 "Unexpected idle time for second connector"))))
386
387 (deftest create-server-acceptor-threads-test
388 (testing "nil acceptors configured properly for http connector"
389 (let [server (create-server-with-partial-http-config
390 {:http {:acceptor-threads nil}})
391 connectors (.getConnectors server)]
392 (is (= 1 (count connectors))
393 "Unexpected number of connectors for server")
394 (is (= (.getAcceptors (ServerConnector. (Server.)))
395 (.getAcceptors (first connectors)))
396 "Unexpected number of acceptor threads for connector")))
397 (testing "non-nil acceptors configured properly for http connector"
398 (let [server (tk-log-testutils/with-test-logging
399 (create-server-with-partial-http-config
400 {:http {:acceptor-threads 42}}))
401 connectors (.getConnectors server)]
402 (is (= 1 (count connectors))
403 "Unexpected number of connectors for server")
404 (is (= 42 (.getAcceptors (first connectors)))
405 "Unexpected number of acceptor threads for connector")))
406 (testing "non-nil acceptors configured properly for multiple connectors"
407 (let [server (tk-log-testutils/with-test-logging
408 (create-server-with-partial-http-and-https-config
409 {:http {:port 25
410 :acceptor-threads 91}
411 :https {:port 92
412 :acceptor-threads 63}}))
413 connectors (.getConnectors server)]
414 (is (= 2 (count connectors))
415 "Unexpected number of connectors for server")
416 (is (= 25 (.getPort (first connectors)))
417 "Unexpected port for first connector")
418 (is (= 91 (.getAcceptors (first connectors)))
419 "Unexpected number of acceptor threads for first connector")
420 (is (= 92 (.getPort (second connectors)))
421 "Unexpected port for second connector")
422 (is (= 63 (.getAcceptors (second connectors)))
423 "Unexpected number of acceptor threads for second connector"))))
424
425 (deftest create-server-selector-threads-test
426 (letfn [(selector-threads [connector]
427 (-> connector
428 (.getSelectorManager)
429 (.getSelectorCount)))]
430 (testing "nil selectors configured properly for http connector"
431 (let [server (create-server-with-partial-http-config
432 {:http {:selector-threads nil}})
433 connectors (.getConnectors server)]
434 (is (= 1 (count connectors))
435 "Unexpected number of connectors for server")
436 (is (= (selector-threads (ServerConnector. (Server.)))
437 (selector-threads (first connectors)))
438 "Unexpected number of selectors for connector")))
439 (testing "non-nil selectors configured properly for http connector"
440 (let [server (create-server-with-partial-http-config
441 {:http {:selector-threads 42}})
442 connectors (.getConnectors server)]
443 (is (= 1 (count connectors))
444 "Unexpected number of connectors for server")
445 (is (= 42 (selector-threads (first connectors)))
446 "Unexpected number of selector threads for connector")))
447 (testing "non-nil selectors configured properly for multiple connectors"
448 (let [server (create-server-with-partial-http-and-https-config
449 {:http {:port 25
450 :selector-threads 91}
451 :https {:port 92
452 :selector-threads 63}})
453 connectors (.getConnectors server)]
454 (is (= 2 (count connectors))
455 "Unexpected number of connectors for server")
456 (is (= 25 (.getPort (first connectors)))
457 "Unexpected port for first connector")
458 (is (= 91 (selector-threads (first connectors)))
459 "Unexpected number of selector threads for first connector")
460 (is (= 92 (.getPort (second connectors)))
461 "Unexpected port for second connector")
462 (is (= 63 (selector-threads (second connectors)))
463 "Unexpected number of selector threads for second connector")))))
464
465 (deftest test-idle-timeout
466 (let [read-lines (fn [r]
467 (let [sb (StringBuilder.)]
468 (loop [l (.readLine r)]
469 (when l
470 (.append sb l)
471 (.append sb "\n")
472 ;; readLine will block until the socket is closed,
473 ;; or will throw a SocketTimeoutException if there
474 ;; is no data available within the SoTimeout value.
475 (recur (.readLine r))))
476 (.toString sb)))
477 body "Hi World\n"
478 path "/hi_world"
479 ring-handler (fn [req] {:status 200 :body body})
480 read-response (fn [client-so-timeout]
481 (let [s (Socket. "localhost" 9000)
482 out (PrintWriter. (.getOutputStream s) true)]
483 (.setSoTimeout s client-so-timeout)
484 (.println out (str "GET " path " HTTP/1.1\n"
485 "Host: localhost\n"
486 "\n"))
487 (let [in (BufferedReader. (InputStreamReader. (.getInputStream s)))]
488 (read-lines in))))]
489 (let [config {:webserver {:port 9000
490 :host "localhost"
491 :idle-timeout-milliseconds 500}}]
492 (with-test-logging
493 (with-app-with-config app
494 [jetty9-service]
495 config
496 (let [s (tk-app/get-service app :WebserverService)
497 add-ring-handler (partial add-ring-handler s)]
498 (add-ring-handler ring-handler path)
499
500 (testing "Verify that server doesn't close socket before idle timeout"
501 ;; if we set the client socket timeout lower than the server
502 ;; socket timeout, we should get a timeout exception from the
503 ;; client side while attempting to read from the socket.
504 (is (thrown-with-msg? SocketTimeoutException #"Read timed out"
505 (read-response 250))))
506 (testing "Verify that server closes the socket after idle timeout"
507 ;; if we set the client socket timeout higher than the server,
508 ;; then the server should close the socket after its timeout,
509 ;; which will cause our read to stop blocking and allow us to
510 ;; validate the contents of the data we read from the socket.
511 (let [resp (read-response 750)]
512 (is (re-find #"(?is)HTTP.*200 OK.*Hi World"
513 resp))))))))))
514
515 (deftest request-body-max-size
516 (let [bigger-post-data (apply str (repeat 21 "f"))
517 smaller-post-data (apply str (repeat 20 "f"))
518 no-request-body-response "no request body"
519 get-request (fn [port]
520 (http-sync/get (format "http://localhost:%d/" port)
521 {:as :text}))
522 post-request (fn [port body]
523 (http-sync/post (format "http://localhost:%d/" port)
524 {:headers
525 {"content-type" "text/plain"}
526 :body body
527 :as :text}))
528 app (fn [req]
529 (let [body (slurp (:body req))]
530 (-> (if (empty? body)
531 no-request-body-response
532 body)
533 (rr/response)
534 (rr/status 200)
535 (rr/content-type "text/plain")
536 (rr/charset "UTF-8"))))]
537 (with-test-webserver-and-config
538 app
539 port
540 {:request-body-max-size 20}
541 (testing "posting data larger than the configured limit fails with 413"
542 (let [response (post-request port bigger-post-data)]
543 (is (= 413 (:status response)))
544 (is (= "" (:body response)))))
545 (testing "posting data within the configured limit succeeds"
546 (let [response (post-request port smaller-post-data)]
547 (is (= 200 (:status response)))
548 (is (= smaller-post-data (:body response)))))
549 (testing "request with no content-length succeeds when limit configured"
550 (let [response (get-request port)]
551 (is (= 200 (:status response)))
552 (is (= no-request-body-response (:body response))))))))
0 (ns puppetlabs.trapperkeeper.services.webserver.jetty9-default-config-test
1 "
2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3 ;; VALIDATION OF DEFAULT JETTY CONFIGURATION VALUES
4 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
5
6 NOTE: IF A TEST IN THIS NAMESPACE FAILS, AND YOU ALTER THE VALUE TO MAKE IT
7 PASS, IT IS YOUR RESPONSIBILITY TO DOUBLE-CHECK THE DOCS TO SEE IF THERE
8 IS ANYWHERE IN THEM THAT THE NEW VALUE NEEDS TO BE ADDED.
9
10 This namespace is a little different than most of our test namespaces. It's
11 not really intended to test any of our own code, it's just here to provide
12 us with a warning in the event that Jetty changes any of the default
13 configuration values.
14
15 In the conversation leading up to https://tickets.puppetlabs.com/browse/TK-168
16 we decided that it was generally not a good idea to be hard-coding our own
17 default values for the settings that we exposed, and that it would be a better
18 idea to allow Jetty to use its implicit default values for any settings that
19 are not explicitly set in a TK config file. Otherwise, we're at risk of
20 the Jetty authors coming up with a really compelling reason to change a
21 default value between releases, and us not picking up that change.
22
23 Therefore, we decided that all the settings we expose should just fall
24 through to Jetty's implicit defaults, and that individual TK application
25 authors can override any appropriate settings in their packaging if needed.
26
27 However, there was some concern that if an upstream Jetty default were to
28 change without us knowing about it, it could have other implications for our
29 applications that we ought to be aware of. Therefore, we agreed that it
30 would be best if we had some way of making sure we could identify when
31 that situation arose.
32
33 That is the purpose of this namespace. It basically provides assertions
34 to validate that we know what Jetty's implicit default value is for all of
35 the settings we expose. If we bump to a new version of Jetty in the future
36 and any of these implicit defaults have changed, these tests will fail. If
37 that happens, we can attempt to evaluate the impact of the change and
38 react accordingly."
39 (:require [clojure.test :refer :all]
40 [schema.test :as schema-test]
41 [puppetlabs.kitchensink.core :as ks]
42 [puppetlabs.trapperkeeper.testutils.bootstrap :refer [with-app-with-config]]
43 [puppetlabs.trapperkeeper.services.webserver.jetty9-service :refer [jetty9-service]]
44 [puppetlabs.trapperkeeper.app :refer [get-service]]
45 [puppetlabs.trapperkeeper.services :refer [service-context]]
46 [puppetlabs.trapperkeeper.services.webserver.jetty9-core :as core]
47 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]
48 [puppetlabs.trapperkeeper.testutils.logging :as tk-log-testutils])
49 (:import (org.eclipse.jetty.server HttpConfiguration ServerConnector Server)
50 (org.eclipse.jetty.util.thread QueuedThreadPool)))
51
52 (use-fixtures :once
53 schema-test/validate-schemas
54 testutils/assert-clean-shutdown)
55
56 (deftest default-request-header-max-size-test
57 (let [http-config (HttpConfiguration.)]
58 ;; See: https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java#L49
59 (is (= 8192 (.getRequestHeaderSize http-config))
60 "Unexpected default for 'request-header-max-size'")))
61
62 (deftest default-proxy-http-client-settings-test
63 (with-app-with-config app
64 [jetty9-service]
65 {:webserver {:host "localhost" :port 8080}}
66 (let [s (get-service app :WebserverService)
67 server-context (get-in (service-context s) [:jetty9-servers :default])
68 proxy-servlet (core/proxy-servlet
69 server-context
70 {:host "localhost"
71 :path "/foo"
72 :port 8080}
73 {})
74 _ (core/add-servlet-handler
75 server-context
76 proxy-servlet
77 "/proxy"
78 {}
79 true
80 false)
81 client (.createHttpClient proxy-servlet)]
82 ;; See: https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java#L129
83 (is (= 4096 (.getRequestBufferSize client))
84 "Unexpected default for proxy 'request-buffer-size'")
85 ;; See: https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java#L268-L271
86 (is (= 30000 (.getIdleTimeout client))
87 "Unexpected default for proxy 'idle-timeout'")
88 (.stop client))))
89
90 (def selector-thread-count
91 "The number of selector threads that should be allocated per connector. See:
92 https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java#L229"
93 (max 1 (min 4 (int (/ (ks/num-cpus) 2)))))
94
95 (def acceptor-thread-count
96 "The number of acceptor threads that should be allocated per connector. See:
97 https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java#L190"
98 (max 1 (min 4 (int (/ (ks/num-cpus) 8)))))
99
100 (deftest default-connector-settings-test
101 (let [connector (ServerConnector. (Server.))]
102 ;; See: https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java#L85
103 (is (= -1 (.getSoLingerTime connector))
104 "Unexpected default for 'so-linger-seconds'")
105 ;; See: https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java#L146
106 (is (= 30000 (.getIdleTimeout connector))
107 "Unexpected default for 'idle-timeout-milliseconds'")
108 (is (= acceptor-thread-count (.getAcceptors connector))
109 "Unexpected default for 'acceptor-threads' and 'ssl-acceptor-threads'")
110 (is (= selector-thread-count
111 (.getSelectorCount (.getSelectorManager connector)))
112 "Unexpected default for 'selector-threads' and 'ssl-selector-threads'")))
113
114 (defn get-max-threads-for-server
115 [server]
116 (.getMaxThreads (.getThreadPool server)))
117
118 (defn get-server-thread-pool-queue
119 [server]
120 (let [thread-pool (.getThreadPool server)
121 ;; Using reflection here because the .getQueue method is protected and I
122 ;; didn't see any other way to pull the queue back from the thread pool.
123 get-queue-method (-> thread-pool
124 (.getClass)
125 (.getDeclaredMethod "getQueue" nil))
126 _ (.setAccessible get-queue-method true)]
127 (.invoke get-queue-method thread-pool nil)))
128
129 (deftest default-server-settings-test
130 (let [server (Server.)]
131 ;; See: https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java#L48
132 (is (= 30000 (.getStopTimeout server))
133 "Unexpected default for 'shutdown-timeout-seconds'")
134 ;; See: https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java#L67
135 (is (= 200 (get-max-threads-for-server server))
136 "Unexpected default for 'max-threads'")
137 ;; See: https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java#L117
138 (is (= (Integer/MAX_VALUE) (.getMaxCapacity
139 (get-server-thread-pool-queue server)))
140 "Unexpected default for 'queue-max-size'")))
141
142 (def threads-per-connector
143 "The total number of threads needed per attached connector."
144 (+ acceptor-thread-count selector-thread-count))
145
146 (defn calculate-minimum-required-threads
147 "Calculate the minimum number threads that a single Jetty Server instance
148 needs. See: https://github.com/eclipse/jetty.project/blob/jetty-9.2.10.v20150310/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java#L334-L350"
149 [connectors]
150 (+ 1 (* connectors threads-per-connector)))
151
152 (deftest default-min-threads-settings-test
153 ;; This test just exists to validate the advice we give for the bare
154 ;; minimum number of threads that one should account for when setting the
155 ;; 'max-threads' setting for a server instance.
156 ;;
157 ;; The tk-jetty9 server configuration allows for either one or two connectors
158 ;; to be associated with a server -- at most one plaintext port connector and
159 ;; at most one encrypted port connector. Because of this, the test only
160 ;; validates the min-threads behavior for a server that has either one or
161 ;; two connectors.
162 (letfn [(get-server [max-threads connectors]
163 (let [server (Server. (QueuedThreadPool. max-threads))]
164 (dotimes [_ connectors]
165 (.addConnector server (ServerConnector. server)))
166 server))
167 (insufficient-threads-msg [server]
168 (let [connectors (count (.getConnectors server))]
169 (re-pattern (str "Insufficient threads: max="
170 (get-max-threads-for-server server)
171 " < needed\\(acceptors="
172 (* acceptor-thread-count connectors)
173 " \\+ selectors="
174 (* selector-thread-count connectors)
175 " \\+ request=1\\)"))))]
176 (dotimes [x 2]
177 (let [connectors (inc x)
178 required-threads (calculate-minimum-required-threads connectors)]
179 (testing (str "server with too few threads for " x " connector(s) "
180 "fail(s) to start with expected error")
181 (let [server (-> required-threads
182 dec
183 (get-server connectors))]
184 (is (thrown-with-msg? IllegalStateException
185 (insufficient-threads-msg server)
186 (tk-log-testutils/with-test-logging
187 (.start server))))))
188 (testing (str "server with minimum required threads for " x
189 "connector(s) start(s) successfully")
190 (let [server (get-server required-threads connectors)]
191 (try
192 (.start server)
193 (is (.isStarted server))
194 (finally
195 (.stop server)))))))))
0 (ns puppetlabs.trapperkeeper.services.webserver.jetty9-service-handlers-test
1 (:import (servlet SimpleServlet)
2 (javax.servlet ServletContextListener)
3 (java.nio.file Paths Files)
4 (java.nio.file.attribute FileAttribute)
5 (javax.servlet.http HttpServlet HttpServletRequest HttpServletResponse))
6 (:require [clojure.test :refer :all]
7 [gniazdo.core :as ws-client]
8 [puppetlabs.experimental.websockets.client :as ws-session]
9 [puppetlabs.trapperkeeper.services.webserver.jetty9-service :refer :all]
10 [puppetlabs.trapperkeeper.testutils.webserver.common :refer :all]
11 [puppetlabs.trapperkeeper.app :refer [get-service]]
12 [puppetlabs.trapperkeeper.testutils.bootstrap :refer [with-app-with-config]]
13 [puppetlabs.trapperkeeper.testutils.logging
14 :refer [with-test-logging]]
15 [schema.test :as schema-test]
16 [clojure.tools.logging :as log]
17 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]))
18
19 (use-fixtures :once
20 schema-test/validate-schemas
21 testutils/assert-clean-shutdown)
22
23 (deftest static-content-test
24 (testing "static content context"
25 (with-app-with-config app
26 [jetty9-service]
27 jetty-plaintext-config
28 (let [s (get-service app :WebserverService)
29 add-context-handler (partial add-context-handler s)
30 path "/resources"
31 resource "logback.xml"]
32 (add-context-handler dev-resources-dir path)
33 (let [response (http-get (str "http://localhost:8080" path "/" resource))]
34 (is (= (:status response) 200))
35 (is (= (:body response) (slurp (str dev-resources-dir resource))))))))
36
37 (testing "static content context with add-context-handler-to"
38 (with-app-with-config app
39 [jetty9-service]
40 jetty-multiserver-plaintext-config
41 (let [s (get-service app :WebserverService)
42 add-context-handler (partial add-context-handler s)
43 path "/resources"
44 resource "logback.xml"]
45 (add-context-handler dev-resources-dir path {:server-id :foo})
46 (let [response (http-get (str "http://localhost:8085" path "/" resource))]
47 (is (= (:status response) 200))
48 (is (= (:body response) (slurp (str dev-resources-dir resource))))))))
49
50 (testing "customization of static content context"
51 (with-app-with-config app
52 [jetty9-service]
53 jetty-plaintext-config
54 (let [s (get-service app :WebserverService)
55 add-context-handler (partial add-context-handler s)
56 path "/resources"
57 body "Hey there"
58 servlet-path "/hey"
59 servlet (SimpleServlet. body)
60 context-listeners [(reify ServletContextListener
61 (contextInitialized [this event]
62 (doto (.addServlet (.getServletContext event) "simple" servlet)
63 (.addMapping (into-array [servlet-path]))))
64 (contextDestroyed [this event]))]]
65 (add-context-handler dev-resources-dir path {:context-listeners context-listeners})
66 (let [response (http-get (str "http://localhost:8080" path servlet-path))]
67 (is (= (:status response) 200))
68 (is (= (:body response) body)))))))
69
70 (deftest add-context-handler-symlinks-test
71 (let [resource "logback.xml"
72 resource-link "logback-link.xml"
73 logback (slurp (str dev-resources-dir resource))
74 link (Paths/get (str dev-resources-dir resource-link) (into-array java.lang.String []))
75 file (Paths/get resource (into-array java.lang.String []))]
76 (try
77 (Files/createSymbolicLink link file (into-array FileAttribute []))
78
79 (testing "symlinks served when :follow-links is true"
80 (with-app-with-config app
81 [jetty9-service]
82 jetty-plaintext-config
83 (let [s (get-service app :WebserverService)
84 add-context-handler (partial add-context-handler s)
85 path "/resources"]
86 (add-context-handler dev-resources-dir path {:follow-links true})
87 (let [response (http-get (str "http://localhost:8080" path "/" resource))]
88 (is (= (:status response) 200))
89 (is (= (:body response) logback)))
90 (let [response (http-get (str "http://localhost:8080" path "/" resource-link))]
91 (is (= (:status response) 200))
92 (is (= (:body response) logback))))))
93
94 (testing "symlinks not served when :follow-links is false"
95 (with-app-with-config app
96 [jetty9-service]
97 jetty-plaintext-config
98 (let [s (get-service app :WebserverService)
99 add-context-handler (partial add-context-handler s)
100 path "/resources"]
101 (add-context-handler dev-resources-dir path {:follow-links false})
102 (let [response (http-get (str "http://localhost:8080" path "/" resource))]
103 (is (= (:status response) 200))
104 (is (= (:body response) logback)))
105 (let [response (http-get (str "http://localhost:8080" path "/" resource-link))]
106 (is (= (:status response) 404))))))
107
108 (finally
109 (Files/delete link)))))
110
111 (deftest servlet-test
112 (testing "request to servlet over http succeeds"
113 (with-app-with-config app
114 [jetty9-service]
115 jetty-plaintext-config
116 (let [s (get-service app :WebserverService)
117 add-servlet-handler (partial add-servlet-handler s)
118 body "Hey there"
119 path "/hey"
120 servlet (SimpleServlet. body)]
121 (add-servlet-handler servlet path)
122 (let [response (http-get
123 (str "http://localhost:8080" path))]
124 (is (= (:status response) 200))
125 (is (= (:body response) body))))))
126
127 (testing "request to servlet over http succeeds with add-servlet-handler-to"
128 (with-app-with-config app
129 [jetty9-service]
130 jetty-multiserver-plaintext-config
131 (let [s (get-service app :WebserverService)
132 add-servlet-handler (partial add-servlet-handler s)
133 body "Hey there"
134 path "/hey"
135 servlet (SimpleServlet. body)]
136 (add-servlet-handler servlet path {:server-id :foo})
137 (let [response (http-get
138 (str "http://localhost:8085" path))]
139 (is (= (:status response) 200))
140 (is (= (:body response) body))))))
141
142 (testing "request to servlet initialized with empty param succeeds"
143 (with-app-with-config app
144 [jetty9-service]
145 jetty-plaintext-config
146 (let [s (get-service app :WebserverService)
147 add-servlet-handler (partial add-servlet-handler s)
148 body "Hey there"
149 path "/hey"
150 servlet (SimpleServlet. body)]
151 (add-servlet-handler servlet path {:servlet-init-params {}})
152 (let [response (http-get (str "http://localhost:8080" path))]
153 (is (= (:status response) 200))
154 (is (= (:body response) body))))))
155
156 (testing "request to servlet initialized with non-empty params succeeds"
157 (with-app-with-config app
158 [jetty9-service]
159 jetty-plaintext-config
160 (let [s (get-service app :WebserverService)
161 add-servlet-handler (partial add-servlet-handler s)
162 body "Hey there"
163 path "/hey"
164 init-param-one "value of init param one"
165 init-param-two "value of init param two"
166 servlet (SimpleServlet. body)]
167 (add-servlet-handler servlet
168 path
169 {:servlet-init-params {"init-param-one" init-param-one
170 "init-param-two" init-param-two}})
171 (let [response (http-get
172 (str "http://localhost:8080" path "/init-param-one"))]
173 (is (= (:status response) 200))
174 (is (= (:body response) init-param-one)))
175 (let [response (http-get
176 (str "http://localhost:8080" path "/init-param-two"))]
177 (is (= (:status response) 200))
178 (is (= (:body response) init-param-two)))))))
179
180 (deftest websocket-test
181 (testing "Websocket handlers"
182 (with-app-with-config app
183 [jetty9-service]
184 jetty-plaintext-config
185 (let [s (get-service app :WebserverService)
186 add-websocket-handler (partial add-websocket-handler s)
187 path "/test"
188 connected (atom 0)
189 server-messages (atom [])
190 server-binary-messages (atom [])
191 client-messages (atom [])
192 client-binary-messages (atom [])
193 client-request-path (atom "")
194 client-remote-addr (atom "")
195 client-is-ssl (atom nil)
196 closed-request-path (atom "")
197 binary-client-message (promise)
198 closed (promise)
199 handlers {:on-connect (fn [ws]
200 (ws-session/send! ws "Hello client!")
201 (swap! connected inc)
202 (reset! client-request-path (ws-session/request-path ws))
203 (reset! client-remote-addr (.. (ws-session/remote-addr ws) (toString)))
204 (reset! client-is-ssl (ws-session/ssl? ws)))
205 :on-text (fn [ws text]
206 (ws-session/send! ws (str "You said: " text))
207 (swap! server-messages conj text))
208 :on-bytes (fn [ws bytes offset len]
209 (let [as-vec (vec bytes)]
210 (ws-session/send! ws (byte-array (reverse as-vec)))
211 (swap! server-binary-messages conj as-vec)))
212 :on-error (fn [ws error]) ;; TODO - Add test for on-error behaviour
213 :on-close (fn [ws code reason] (swap! connected dec)
214 (reset! closed-request-path (ws-session/request-path ws))
215 (deliver closed true))}]
216 (add-websocket-handler handlers path)
217 (let [socket (ws-client/connect (str "ws://localhost:8080" path "/foo")
218 :on-receive (fn [text] (swap! client-messages conj text))
219 :on-binary (fn [bytes offset len]
220 (let [as-vec (vec bytes)]
221 (swap! client-binary-messages conj as-vec)
222 (deliver binary-client-message true))))]
223 (ws-client/send-msg socket "Hello websocket handler")
224 (ws-client/send-msg socket "You look dandy")
225 (ws-client/send-msg socket (byte-array [2 1 2 3 3]))
226 (deref binary-client-message)
227 (is (= @connected 1))
228 (is (= @client-request-path "/foo"))
229 (is (re-matches #"/127\.0\.0\.1:\d+" @client-remote-addr))
230 (is (= @client-is-ssl false))
231 (ws-client/close socket)
232 (deref closed)
233 (is (= @closed-request-path "/foo"))
234 (is (= @connected 0))
235 (is (= @server-binary-messages [[2 1 2 3 3]]))
236 (is (= @client-binary-messages [[3 3 2 1 2]]))
237 (is (= @client-messages ["Hello client!"
238 "You said: Hello websocket handler"
239 "You said: You look dandy"]))
240 (is (= @server-messages ["Hello websocket handler"
241 "You look dandy"]))))))
242 (testing "can close without supplying a reason"
243 (with-app-with-config app
244 [jetty9-service]
245 jetty-plaintext-config
246 (let [s (get-service app :WebserverService)
247 add-websocket-handler (partial add-websocket-handler s)
248 path "/test"
249 closed (promise)
250 handlers {:on-connect (fn [ws] (ws-session/close! ws))}]
251 (add-websocket-handler handlers path)
252 (let [socket (ws-client/connect (str "ws://localhost:8080" path)
253 :on-close (fn [code reason] (deliver closed code)))]
254 ;; 1000 is for normal closure https://tools.ietf.org/html/rfc6455#section-7.4.1
255 (is (= 1000 @closed))))))
256 (testing "can close with reason"
257 (with-app-with-config app
258 [jetty9-service]
259 jetty-plaintext-config
260 (let [s (get-service app :WebserverService)
261 add-websocket-handler (partial add-websocket-handler s)
262 path "/test"
263 closed (promise)
264 handlers {:on-connect (fn [ws] (ws-session/close! ws 4000 "Bye"))}]
265 (add-websocket-handler handlers path)
266 (let [socket (ws-client/connect (str "ws://localhost:8080" path)
267 :on-close (fn [code reason] (deliver closed [code reason])))]
268 (is (= [4000 "Bye"] @closed)))))))
269
270 (deftest war-test
271 (testing "WAR support"
272 (with-app-with-config app
273 [jetty9-service]
274 jetty-plaintext-config
275 (let [s (get-service app :WebserverService)
276 add-war-handler (partial add-war-handler s)
277 path "/test"
278 war "helloWorld.war"]
279 (add-war-handler (str dev-resources-dir war) path)
280 (let [response (http-get (str "http://localhost:8080" path "/hello"))]
281 (is (= (:status response) 200))
282 (is (= (:body response)
283 "<html>\n<head><title>Hello World Servlet</title></head>\n<body>Hello World!!</body>\n</html>\n"))))))
284
285 (testing "WAR support with add-war-handler-to"
286 (with-app-with-config app
287 [jetty9-service]
288 jetty-multiserver-plaintext-config
289 (let [s (get-service app :WebserverService)
290 add-war-handler (partial add-war-handler s)
291 path "/test"
292 war "helloWorld.war"]
293 (add-war-handler (str dev-resources-dir war) path {:server-id :foo})
294 (let [response (http-get (str "http://localhost:8085" path "/hello"))]
295 (is (= (:status response) 200))
296 (is (= (:body response)
297 "<html>\n<head><title>Hello World Servlet</title></head>\n<body>Hello World!!</body>\n</html>\n")))))))
298
299 (deftest endpoints-test
300 (testing "Retrieve all endpoints"
301 (with-app-with-config app
302 [jetty9-service]
303 jetty-plaintext-config
304 (let [s (get-service app :WebserverService)
305 path-context "/ernie"
306 path-context2 "/gonzo"
307 path-context3 "/goblinking"
308 path-ring "/bert"
309 path-servlet "/foo"
310 path-war "/bar"
311 path-proxy "/baz"
312 path-websocket "/quux"
313 get-registered-endpoints (partial get-registered-endpoints s)
314 add-context-handler (partial add-context-handler s)
315 add-ring-handler (partial add-ring-handler s)
316 add-servlet-handler (partial add-servlet-handler s)
317 add-war-handler (partial add-war-handler s)
318 add-proxy-route (partial add-proxy-route s)
319 add-websocket-handler (partial add-websocket-handler s)
320 ring-handler (fn [req] {:status 200 :body "Hi world"})
321 body "This is a test"
322 servlet (SimpleServlet. body)
323 context-listeners [(reify ServletContextListener
324 (contextInitialized [this event]
325 (doto (.addServlet (.getServletContext event) "simple" servlet)
326 (.addMapping (into-array [path-servlet]))))
327 (contextDestroyed [this event]))]
328 war "helloWorld.war"
329 websocket-handlers {:on-connect (fn [ws])}
330 target {:host "0.0.0.0"
331 :port 9000
332 :path "/ernie"}
333 target2 {:host "localhost"
334 :port 10000
335 :path "/kermit"}]
336 (add-context-handler dev-resources-dir path-context)
337 (add-context-handler dev-resources-dir path-context2 {:context-listeners []})
338 (add-context-handler dev-resources-dir path-context3 {:context-listeners context-listeners})
339 (add-ring-handler ring-handler path-ring)
340 (add-servlet-handler servlet path-servlet)
341 (add-war-handler (str dev-resources-dir war) path-war)
342 (add-proxy-route target path-proxy)
343 (add-proxy-route target2 path-proxy {})
344 (add-websocket-handler websocket-handlers path-websocket)
345 (let [endpoints (get-registered-endpoints)]
346 (is (= endpoints {"/ernie" [{:type :context :base-path dev-resources-dir
347 :context-listeners []}]
348 "/gonzo" [{:type :context :base-path dev-resources-dir
349 :context-listeners []}]
350 "/goblinking" [{:type :context :base-path dev-resources-dir
351 :context-listeners context-listeners}]
352 "/bert" [{:type :ring}]
353 "/foo" [{:type :servlet :servlet (type servlet)}]
354 "/bar" [{:type :war :war-path (str dev-resources-dir war)}]
355 "/baz" [{:type :proxy :target-host "0.0.0.0" :target-port 9000
356 :target-path "/ernie"}
357 {:type :proxy :target-host "localhost" :target-port 10000
358 :target-path "/kermit"}]
359 "/quux" [{:type :websocket}]}))))))
360
361 (testing "Log endpoints"
362 (with-test-logging
363 (with-app-with-config app
364 [jetty9-service]
365 jetty-multiserver-plaintext-config
366 (let [s (get-service app :WebserverService)
367 log-registered-endpoints (partial log-registered-endpoints s)
368 add-ring-handler (partial add-ring-handler s)
369 ring-handler (fn [req] {:status 200 :body "Hi world"})
370 path-ring "/bert"]
371 (add-ring-handler ring-handler path-ring)
372 (log-registered-endpoints)
373 (is (logged? #"^\{\"\/bert\" \[\{:type :ring\}\]\}$"))
374 (is (logged? #"^\{\"\/bert\" \[\{:type :ring\}\]\}$" :info)))))))
375
376 (deftest trailing-slash-redirect-test
377 (testing "redirects when no trailing slash is present are disabled by default"
378 (with-app-with-config app
379 [jetty9-service]
380 jetty-plaintext-config
381 (let [s (get-service app :WebserverService)
382 add-ring-handler (partial add-ring-handler s)
383 ring-handler (fn [req] {:status 200 :body "Hi world"})
384 path "/hello"]
385 (add-ring-handler ring-handler path)
386 (let [response (http-get "http://localhost:8080/hello" {:as :text
387 :follow-redirects false})]
388 (is (= (:status response) 200))
389 (is (= (:body response) "Hi world"))
390 (is (= (get-in response [:opts :url]) "http://localhost:8080/hello"))))))
391
392 (testing "redirects when no trailing slash is present and option is enabled"
393 (with-app-with-config app
394 [jetty9-service]
395 jetty-plaintext-config
396 (let [s (get-service app :WebserverService)
397 add-ring-handler (partial add-ring-handler s)
398 ring-handler (fn [req] {:status 200 :body "Hi world"})
399 path "/hello"]
400 (add-ring-handler ring-handler path {:redirect-if-no-trailing-slash true})
401 (let [response (http-get "http://localhost:8080/hello" {:as :text
402 :follow-redirects false})]
403 (is (= (:status response) 302))
404 (is (= (get-in response [:headers "location"]) "http://localhost:8080/hello/"))
405 (is (= (get-in response [:opts :url]) "http://localhost:8080/hello")))))))
406
407 (defn ring-handler-echoing-request-uri
408 []
409 (fn [req] {:status 200 :body (:uri req)}))
410
411 (deftest normalize-request-uri-enabled-for-ring-handler-test
412 (testing "when uri request normalization enabled for ring handler"
413 (with-app-with-config
414 app
415 [jetty9-service]
416 jetty-plaintext-config
417 (let [webserver-service (get-service app :WebserverService)]
418 (add-ring-handler webserver-service
419 (ring-handler-echoing-request-uri)
420 "/hello"
421 {:normalize-request-uri true})
422 (testing "uri with encoded characters is properly decoded"
423 (let [response (http-get "http://localhost:8080/hello%2f%2f%77o%72l%64"
424 {:as :text})]
425 (is (= (:status response) 200))
426 (is (= (:body response) "/hello/world"))))
427 (testing "uri with relative path above root is rejected"
428 (let [response
429 (http-get
430 "http://localhost:8080/hello/world/%2E%2E/%2E%2E/%2E%2E/cleveland"
431 {:as :text})]
432 (is (= (:status response) 400))))
433 (testing "uri with relative path below root is rejected"
434 (let [response (http-get
435 "http://localhost:8080/hello/world/%2E%2E/cleveland"
436 {:as :text})]
437 (is (= (:status response) 400))))))))
438
439 (deftest normalize-request-uri-disabled-for-ring-handler-test
440 (testing "when uri request normalization disabled for ring handler"
441 (with-app-with-config
442 app
443 [jetty9-service]
444 jetty-plaintext-config
445 (let [webserver-service (get-service app :WebserverService)]
446 (add-ring-handler webserver-service
447 (ring-handler-echoing-request-uri)
448 "/hello"
449 {:normalize-request-uri false})
450 (testing "uri with encoded characters is properly decoded"
451 (let [response (http-get "http://localhost:8080/hello%2f%2f%77o%72l%64"
452 {:as :text})]
453 (is (= (:status response) 200))
454 (is (= (:body response) "/hello%2f%2f%77o%72l%64"))))
455 (testing "uri with relative path above root is rejected"
456 (let [response
457 (http-get
458 "http://localhost:8080/hello/world/%2E%2E/%2E%2E/%2E%2E/cleveland"
459 {:as :text})]
460 (is (= (:status response) 400))))
461 (testing "uri with relative path below root is resolved"
462 (let [response (http-get
463 "http://localhost:8080/hello/world/%2E%2E/cleveland"
464 {:as :text})]
465 (is (= (:status response) 200))
466 (is (= (:body response) "/hello/world/%2E%2E/cleveland"))))))))
467
468 (defn servlet-echoing-request-uri
469 []
470 (proxy [HttpServlet] []
471 (doGet [^HttpServletRequest request
472 ^HttpServletResponse response]
473 (-> response
474 (.getWriter)
475 (.print (.getRequestURI request)))
476 (.setStatus response 200))))
477
478 (deftest normalize-request-uri-enabled-for-servlet-test
479 (testing "when uri request normalization enabled for servlet"
480 (with-app-with-config
481 app
482 [jetty9-service]
483 jetty-plaintext-config
484 (let [webserver-service (get-service app :WebserverService)]
485 (add-servlet-handler
486 webserver-service
487 (servlet-echoing-request-uri)
488 "/hello"
489 {:normalize-request-uri true})
490 (testing "uri with encoded characters is properly decoded"
491 (let [response (http-get "http://localhost:8080/hello%2f%2f%77o%72l%64"
492 {:as :text})]
493 (is (= (:status response) 200))
494 (is (= (:body response) "/hello/world"))))
495 (testing "uri with relative path above root is rejected"
496 (let [response
497 (http-get
498 "http://localhost:8080/hello/world/%2E%2E/%2E%2E/%2E%2E/cleveland"
499 {:as :text})]
500 (is (= (:status response) 400))))
501 (testing "uri with relative path below root is rejected"
502 (let [response (http-get
503 "http://localhost:8080/hello/world/%2E%2E/cleveland"
504 {:as :text})]
505 (is (= (:status response) 400))))))))
506
507 (deftest normalize-request-uri-disabled-for-servlet-test
508 (testing "when uri request normalization disabled for servlet"
509 (with-app-with-config
510 app
511 [jetty9-service]
512 jetty-plaintext-config
513 (let [webserver-service (get-service app :WebserverService)]
514 (add-servlet-handler
515 webserver-service
516 (servlet-echoing-request-uri)
517 "/hello"
518 {:normalize-request-uri false})
519 (testing "uri with encoded characters is not decoded"
520 (let [response (http-get "http://localhost:8080/hello%2f%2f%77o%72l%64"
521 {:as :text})]
522 (is (= (:status response) 200))
523 (is (= (:body response) "/hello%2f%2f%77o%72l%64"))))
524 (testing "uri with relative path above root is rejected"
525 (let [response
526 (http-get
527 "http://localhost:8080/hello/world/%2E%2E/%2E%2E/%2E%2E/cleveland"
528 {:as :text})]
529 (is (= (:status response) 400))))
530 (testing "uri with relative path below root is resolved"
531 (let [response (http-get
532 "http://localhost:8080/hello/world/%2E%2E/cleveland"
533 {:as :text})]
534 (is (= (:status response) 200))
535 (is (= (:body response) "/hello/world/%2E%2E/cleveland"))))))))
0 (ns puppetlabs.trapperkeeper.services.webserver.jetty9-service-override-settings-test
1 (:require [clojure.test :refer :all]
2 [puppetlabs.trapperkeeper.app :refer [get-service]]
3 [puppetlabs.trapperkeeper.services :as tk-services]
4 [puppetlabs.trapperkeeper.services.webserver.jetty9-service
5 :refer :all]
6 [puppetlabs.trapperkeeper.testutils.webserver.common :refer :all]
7 [puppetlabs.trapperkeeper.testutils.bootstrap
8 :refer [with-app-with-config]]
9 [puppetlabs.trapperkeeper.testutils.logging
10 :refer [with-test-logging]]
11 [schema.test :as schema-test]
12 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]))
13
14 (use-fixtures :once
15 schema-test/validate-schemas
16 testutils/assert-clean-shutdown)
17
18 (def dev-resources-config-dir (str dev-resources-dir "config/jetty/"))
19
20 (def jetty-ssl-no-certs-config
21 {:webserver {:ssl-host "0.0.0.0"
22 :ssl-port 9001}})
23
24 (def jetty-plaintext-multiserver-override-config
25 {:webserver {:bar {:port 8080
26 :default-server true}
27 :foo {:port 9000}}})
28
29
30 (deftest test-override-webserver-settings!
31 (let [ssl-port 9001
32 overrides {:ssl-port ssl-port
33 :ssl-host "0.0.0.0"
34 :ssl-cert
35 (str dev-resources-config-dir
36 "ssl/certs/localhost.pem")
37 :ssl-cert-chain
38 (str dev-resources-config-dir
39 "ssl/certs/ca.pem")
40 :ssl-key
41 (str dev-resources-config-dir
42 "ssl/private_keys/localhost.pem")
43 :ssl-ca-cert
44 (str dev-resources-config-dir
45 "ssl/certs/ca.pem")
46 :ssl-crl-path
47 (str dev-resources-config-dir
48 "ssl/crls/crls_none_revoked.pem")}]
49 (testing "config override of all SSL settings before webserver starts is
50 successful"
51 (let [override-result (atom nil)
52 service1 (tk-services/service
53 [[:WebserverService override-webserver-settings!]]
54 (init [this context]
55 (reset! override-result
56 (override-webserver-settings!
57 overrides))
58 context))]
59 (with-test-logging
60 (with-app-with-config
61 app
62 [jetty9-service service1]
63 jetty-plaintext-multiserver-override-config
64 (let [s (get-service app :WebserverService)
65 add-ring-handler (partial add-ring-handler s)
66 body "Hi World"
67 path "/hi_world"
68 ring-handler (fn [req] {:status 200 :body body})]
69 (add-ring-handler ring-handler path)
70 (let [response (http-get
71 (format "https://localhost:%d%s/" ssl-port path)
72 default-options-for-https-client)]
73 (is (= (:status response) 200)
74 "Unsuccessful http response code ring handler response.")
75 (is (= (:body response) body)
76 "Unexpected body in ring handler response."))))
77 (is (logged? #"^webserver config overridden for key 'ssl-port'")
78 "Didn't find log message for override of 'ssl-port'")
79 (is (logged? #"^webserver config overridden for key 'ssl-host'")
80 "Didn't find log message for override of 'ssl-host'")
81 (is (logged? #"^webserver config overridden for key 'ssl-cert'")
82 "Didn't find log message for override of 'ssl-cert'")
83 (is (logged?
84 #"^webserver config overridden for key 'ssl-cert-chain'")
85 "Didn't find log message for override of 'ssl-cert-chain'")
86 (is (logged? #"^webserver config overridden for key 'ssl-key'")
87 "Didn't find log message for override of 'ssl-key'")
88 (is (logged? #"^webserver config overridden for key 'ssl-ca-cert'")
89 "Didn't find log message for override of 'ssl-ca-cert'")
90 (is (logged? #"^webserver config overridden for key 'ssl-crl-path'")
91 "Didn't find log message for override of 'ssl-crl-path'"))
92 (is (= overrides @override-result)
93 "Unexpected response to override-webserver-settings! call.")))
94 (testing "config override of all SSL settings before webserver starts is
95 successful when specifying a specific server"
96 (let [override-result (atom nil)
97 service1 (tk-services/service
98 [[:WebserverService override-webserver-settings!]]
99 (init [this context]
100 (reset! override-result
101 (override-webserver-settings!
102 :foo overrides))
103 context))]
104 (with-test-logging
105 (with-app-with-config
106 app
107 [jetty9-service service1]
108 jetty-multiserver-plaintext-config
109 (let [s (get-service app :WebserverService)
110 add-ring-handler (partial add-ring-handler s)
111 body "Hi World"
112 path "/hi_world"
113 ring-handler (fn [req] {:status 200 :body body})]
114 (add-ring-handler ring-handler path {:server-id :foo})
115 (let [response (http-get
116 (format "https://localhost:%d%s/" ssl-port path)
117 default-options-for-https-client)]
118 (is (= (:status response) 200)
119 "Unsuccessful http response code ring handler response.")
120 (is (= (:body response) body)
121 "Unexpected body in ring handler response."))))
122 (is (logged? #"^webserver config overridden for key 'ssl-port'")
123 "Didn't find log message for override of 'ssl-port'")
124 (is (logged? #"^webserver config overridden for key 'ssl-host'")
125 "Didn't find log message for override of 'ssl-host'")
126 (is (logged? #"^webserver config overridden for key 'ssl-cert'")
127 "Didn't find log message for override of 'ssl-cert'")
128 (is (logged? #"^webserver config overridden for key 'ssl-key'")
129 "Didn't find log message for override of 'ssl-key'")
130 (is (logged? #"^webserver config overridden for key 'ssl-ca-cert'")
131 "Didn't find log message for override of 'ssl-ca-cert'")
132 (is (logged? #"^webserver config overridden for key 'ssl-crl-path'")
133 "Didn't find log message for override of 'ssl-crl-path'"))
134 (is (= overrides @override-result)
135 "Unexpected response to override-webserver-settings! call.")))
136 (testing "SSL certificate settings can be overridden while other settings
137 from the config are still honored -- ssl-port and ssl-host"
138 (let [override-result (atom nil)
139 overrides {:ssl-cert
140 (str dev-resources-config-dir
141 "ssl/certs/localhost.pem")
142 :ssl-key
143 (str dev-resources-config-dir
144 "ssl/private_keys/localhost.pem")
145 :ssl-ca-cert
146 (str dev-resources-config-dir
147 "ssl/certs/ca.pem")}
148 service1 (tk-services/service
149 [[:WebserverService override-webserver-settings!]]
150 (init [this context]
151 (reset! override-result
152 (override-webserver-settings!
153 overrides))
154 context))]
155 (with-app-with-config
156 app
157 [jetty9-service service1]
158 jetty-ssl-no-certs-config
159 (let [s (get-service app :WebserverService)
160 add-ring-handler (partial add-ring-handler s)
161 body "Hi World"
162 path "/hi_world"
163 ring-handler (fn [req] {:status 200 :body body})]
164 (add-ring-handler ring-handler path)
165 (let [response (http-get
166 (format "https://localhost:%d%s/" ssl-port path)
167 default-options-for-https-client)]
168 (is (= (:status response) 200)
169 "Unsuccessful http response code ring handler response.")
170 (is (= (:body response) body)
171 "Unexpected body in ring handler response."))))
172 (is (= overrides @override-result)
173 "Unexpected response to override-webserver-settings! call.")))
174 (testing "attempt to override SSL settings fails when override call made
175 after webserver has already started"
176 (let [override-result (atom nil)
177 service1 (tk-services/service [])]
178 (with-app-with-config
179 app
180 [jetty9-service service1]
181 jetty-plaintext-config
182 (let [s (get-service app :WebserverService)
183 override-webserver-settings! (partial
184 override-webserver-settings!
185 s)]
186 (is (thrown-with-msg? java.lang.IllegalStateException
187 #"overrides cannot be set because webserver has already processed the config"
188 (override-webserver-settings! overrides)))))))
189 (testing "second attempt to override SSL settings fails"
190 (let [second-override-result (atom nil)
191 service1 (tk-services/service
192 [[:WebserverService
193 override-webserver-settings!]]
194 (init [this context]
195 (override-webserver-settings!
196 overrides)
197 (reset!
198 second-override-result
199 (is
200 (thrown-with-msg?
201 IllegalStateException
202 #"overrides cannot be set because they have already been set"
203 (override-webserver-settings!
204 overrides))))
205 context))]
206 (with-app-with-config
207 app
208 [jetty9-service service1]
209 jetty-plaintext-config
210 (let [s (get-service app :WebserverService)
211 add-ring-handler (partial add-ring-handler s)
212 body "Hi World"
213 path "/hi_world"
214 ring-handler (fn [req] {:status 200 :body body})]
215 (add-ring-handler ring-handler path)
216 (let [response (http-get
217 (format "https://localhost:%d%s/" ssl-port path)
218 default-options-for-https-client)]
219 (is (= (:status response) 200)
220 "Unsuccessful http response code ring handler response.")
221 (is (= (:body response) body)
222 "Unexpected body in ring handler response."))))
223 (is (instance? IllegalStateException @second-override-result)
224 "Second call to setting overrides did not throw expected exception.")))))
0 (ns puppetlabs.trapperkeeper.services.webserver.jetty9-service-proxy-test
1 (:import [java.net URI])
2 (:require [clojure.test :refer :all]
3 [puppetlabs.trapperkeeper.services.webserver.jetty9-service :refer :all]
4 [puppetlabs.trapperkeeper.testutils.webserver.common :refer :all]
5 [puppetlabs.trapperkeeper.app :refer [get-service]]
6 [puppetlabs.trapperkeeper.services :refer [service]]
7 [puppetlabs.trapperkeeper.testutils.bootstrap :refer [with-app-with-config]]
8 [ring.middleware.params :as ring-params]
9 [schema.test :as schema-test]
10 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]))
11
12 (use-fixtures :once
13 schema-test/validate-schemas
14 testutils/assert-clean-shutdown)
15
16 (defn query-params-handler
17 [req]
18 {:status 200
19 :body (str (:query-params req))})
20
21 (def app-wrapped
22 (ring-params/wrap-params query-params-handler))
23
24 (defn proxy-ring-handler
25 [req]
26 (condp = (:uri req)
27 "/hello/world" {:status 200 :body (str "Hello, World!"
28 ((:headers req) "x-fancy-proxy-header")
29 ((:headers req) "cookie"))}
30 "/hello/earth" {:status 200 :body (str "Hello, Earth!"
31 ((:headers req) "x-fancy-proxy-header")
32 ((:headers req) "cookie"))}
33 {:status 404 :body "D'oh"}))
34
35 (defn redirect-test-handler
36 [req]
37 (condp = (:uri req)
38 "/hello/world" {:status 200 :body "Hello, World!"}
39 "/hello/" {:status 302
40 :headers {"Location" "/hello/world"}
41 :body ""}
42 {:status 404 :body "D'oh"}))
43
44 (defn redirect-wrong-host
45 [req]
46 {:status 302
47 :headers {"Location" "http://fakehost:5/hello"}
48 :body ""})
49
50 (defn redirect-same-host
51 [req]
52 (condp = (:uri req)
53 "/hello/world" {:status 200 :body "Hello, World!"}
54 "/hello/" {:status 302
55 :headers {"Location" "http://localhost:9000/hello/world"}
56 :body ""}
57 {:status 404 :body "D'oh"}))
58
59 (defn redirect-different-proxy-path
60 [req]
61 (condp = (:uri req)
62 "/goodbye/world" {:status 200 :body "Hello, World!"}
63 "/hello/" {:status 302
64 :headers {"Location" "http://localhost:9000/goodbye/world"}
65 :body ""}
66 {:status 404 :body "D'oh"}))
67
68 (defn ring-handler-with-sleep
69 "Makes a ring handler which sleeps for a set amount of milliseconds before
70 responding. This is used to test timeout settings."
71 [sleep-time]
72 (fn [_]
73 (Thread/sleep sleep-time)
74 {:status 200
75 :body "This should have timed out."}))
76
77 (defprotocol TkProxyService)
78
79 (defn proxy-service
80 [proxy-config proxy-opts proxy-path]
81 (service TkProxyService
82 [[:WebserverService add-proxy-route]]
83 (init [this context]
84 (if proxy-opts
85 (add-proxy-route proxy-config
86 proxy-path
87 proxy-opts)
88 (add-proxy-route proxy-config
89 proxy-path))
90 context)))
91
92 (defmacro with-target-and-proxy-servers
93 [{:keys [target proxy proxy-config proxy-opts ring-handler
94 register-proxy-route-before-server-start?]} & body]
95 (let [proxy-path "/hello-proxy"]
96 `(with-app-with-config proxy-target-app#
97 [jetty9-service]
98 {:webserver ~target}
99 (let [target-webserver# (get-service proxy-target-app# :WebserverService)]
100 (add-ring-handler
101 target-webserver#
102 ~ring-handler
103 "/hello")
104 (add-ring-handler
105 target-webserver#
106 ~ring-handler
107 "/goodbye"))
108 (if ~register-proxy-route-before-server-start?
109 (let [proxy-service# (proxy-service ~proxy-config
110 ~proxy-opts
111 ~proxy-path)]
112 (with-app-with-config proxy-app#
113 [jetty9-service proxy-service#]
114 {:webserver ~proxy}
115 ~@body))
116 (with-app-with-config proxy-app#
117 [jetty9-service]
118 {:webserver ~proxy}
119 (let [proxy-webserver# (get-service proxy-app# :WebserverService)]
120 (if ~proxy-opts
121 (add-proxy-route proxy-webserver#
122 ~proxy-config
123 ~proxy-path
124 ~proxy-opts)
125 (add-proxy-route proxy-webserver#
126 ~proxy-config
127 ~proxy-path)))
128 ~@body)))))
129
130 (def common-ssl-config
131 {:ssl-cert "./dev-resources/config/jetty/ssl/certs/localhost.pem"
132 :ssl-key "./dev-resources/config/jetty/ssl/private_keys/localhost.pem"
133 :ssl-ca-cert "./dev-resources/config/jetty/ssl/certs/ca.pem"})
134
135 (defn rewrite-uri-callback-fn
136 [target-uri req]
137 (URI.
138 (.getScheme target-uri)
139 nil
140 (.getHost target-uri)
141 (.getPort target-uri)
142 "/hello/earth"
143 nil nil))
144
145 (defn callback-fn
146 [proxy-req req]
147 (.header proxy-req "x-fancy-proxy-header" "!!!"))
148
149 (defn failure-callback-fn
150 [req resp proxy-resp failure]
151 (.setStatus resp 500)
152 (.print (.getWriter resp) (str "Proxying failed: " (.getMessage failure))))
153
154 (deftest test-basic-proxy-support
155 (testing "basic proxy support when proxy handler registered after server start"
156 (with-target-and-proxy-servers
157 {:target {:host "0.0.0.0"
158 :port 9000}
159 :proxy {:host "0.0.0.0"
160 :port 10000}
161 :proxy-config {:host "localhost"
162 :port 9000
163 :path "/hello"}
164 :ring-handler proxy-ring-handler}
165 (let [response (http-get "http://localhost:9000/hello/world")]
166 (is (= (:status response) 200))
167 (is (= (:body response) "Hello, World!")))
168 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
169 (is (= (:status response) 200))
170 (is (= (:body response) "Hello, World!")))))
171
172 (testing "basic proxy support when proxy handler registered before server start"
173 (with-target-and-proxy-servers
174 {:target {:host "0.0.0.0"
175 :port 9000}
176 :proxy {:host "0.0.0.0"
177 :port 10000}
178 :proxy-config {:host "localhost"
179 :port 9000
180 :path "/hello"}
181 :ring-handler proxy-ring-handler
182 :register-proxy-route-before-server-start? true}
183 (let [response (http-get "http://localhost:9000/hello/world")]
184 (is (= (:status response) 200))
185 (is (= (:body response) "Hello, World!")))
186 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
187 (is (= (:status response) 200))
188 (is (= (:body response) "Hello, World!")))))
189
190 (testing "basic proxy support with add-proxy-route-to"
191 (with-target-and-proxy-servers
192 {:target {:host "0.0.0.0"
193 :port 9000}
194 :proxy {:foo {:host "0.0.0.0"
195 :port 10000}
196 :bar {:host "0.0.0.0"
197 :port 8085
198 :default-server true}}
199 :proxy-config {:host "localhost"
200 :port 9000
201 :path "/hello"}
202 :proxy-opts {:server-id :foo}
203 :ring-handler proxy-ring-handler}
204 (let [response (http-get "http://localhost:9000/hello/world")]
205 (is (= (:status response) 200))
206 (is (= (:body response) "Hello, World!")))
207 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
208 (is (= (:status response) 200))
209 (is (= (:body response) "Hello, World!"))))))
210
211 (deftest proxy-large-cookie
212 (testing "proxy does not explode on a large cookie when properly configured"
213 (with-target-and-proxy-servers
214 {:target {:host "0.0.0.0"
215 :port 9000
216 :request-header-max-size 16192}
217 :proxy {:host "0.0.0.0"
218 :port 10000
219 :request-header-max-size 16192}
220 :proxy-config {:host "localhost"
221 :port 9000
222 :path "/hello"}
223 :proxy-opts {:request-buffer-size 16192}
224 :ring-handler proxy-ring-handler}
225 (let [response (http-get "http://localhost:9000/hello/world")]
226 (is (= (:status response) 200))
227 (is (= (:body response) "Hello, World!")))
228 (let [response (http-get "http://localhost:10000/hello-proxy/world"
229 {:headers {"Cookie" absurdly-large-cookie}
230 :as :text})]
231 (is (= (:status response) 200))
232 (is (= (:body response) (str "Hello, World!" absurdly-large-cookie)))))))
233
234 (deftest proxy-with-orig-scheme
235 (testing "basic proxy support with explicit :orig scheme"
236 (with-target-and-proxy-servers
237 {:target {:host "0.0.0.0"
238 :port 9000}
239 :proxy {:host "0.0.0.0"
240 :port 10000}
241 :proxy-config {:host "localhost"
242 :port 9000
243 :path "/hello"}
244 :proxy-opts {:scheme :orig}
245 :ring-handler proxy-ring-handler}
246 (let [response (http-get "http://localhost:9000/hello/world")]
247 (is (= (:status response) 200))
248 (is (= (:body response) "Hello, World!")))
249 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
250 (is (= (:status response) 200))
251 (is (= (:body response) "Hello, World!")))))
252
253 (testing "basic proxy support with explicit \"orig\" scheme as string"
254 (with-target-and-proxy-servers
255 {:target {:host "0.0.0.0"
256 :port 9000}
257 :proxy {:host "0.0.0.0"
258 :port 10000}
259 :proxy-config {:host "localhost"
260 :port 9000
261 :path "/hello"}
262 :proxy-opts {:scheme "orig"}
263 :ring-handler proxy-ring-handler}
264 (let [response (http-get "http://localhost:9000/hello/world")]
265 (is (= (:status response) 200))
266 (is (= (:body response) "Hello, World!")))
267 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
268 (is (= (:status response) 200))
269 (is (= (:body response) "Hello, World!"))))))
270
271 (deftest basic-https-proxy
272 (testing "basic https proxy support (pass-through https config)"
273 (with-target-and-proxy-servers
274 {:target (merge common-ssl-config
275 {:ssl-host "0.0.0.0"
276 :ssl-port 9001})
277 :proxy (merge common-ssl-config
278 {:ssl-host "0.0.0.0"
279 :ssl-port 10001})
280 :proxy-config {:host "localhost"
281 :port 9001
282 :path "/hello"}
283 :ring-handler proxy-ring-handler}
284 (let [response (http-get "https://localhost:9001/hello/world" default-options-for-https-client)]
285 (is (= (:status response) 200))
286 (is (= (:body response) "Hello, World!")))
287 (let [response (http-get "https://localhost:10001/hello-proxy/world" default-options-for-https-client)]
288 (is (= (:status response) 200))
289 (is (= (:body response) "Hello, World!")))))
290
291 (testing "basic https proxy support (pass-through https config) with explicit :orig scheme"
292 (with-target-and-proxy-servers
293 {:target (merge common-ssl-config
294 {:ssl-host "0.0.0.0"
295 :ssl-port 9001})
296 :proxy (merge common-ssl-config
297 {:ssl-host "0.0.0.0"
298 :ssl-port 10001})
299 :proxy-config {:host "localhost"
300 :port 9001
301 :path "/hello"}
302 :proxy-opts {:scheme :orig}
303 :ring-handler proxy-ring-handler}
304 (let [response (http-get "https://localhost:9001/hello/world" default-options-for-https-client)]
305 (is (= (:status response) 200))
306 (is (= (:body response) "Hello, World!")))
307 (let [response (http-get "https://localhost:10001/hello-proxy/world" default-options-for-https-client)]
308 (is (= (:status response) 200))
309 (is (= (:body response) "Hello, World!")))))
310
311 (testing "basic https proxy support (pass-through https config via explicit :use-server-config)"
312 (with-target-and-proxy-servers
313 {:target (merge common-ssl-config
314 {:ssl-host "0.0.0.0"
315 :ssl-port 9001})
316 :proxy (merge common-ssl-config
317 {:ssl-host "0.0.0.0"
318 :ssl-port 10001})
319 :proxy-config {:host "localhost"
320 :port 9001
321 :path "/hello"}
322 :proxy-opts {:ssl-config :use-server-config}
323 :ring-handler proxy-ring-handler}
324 (let [response (http-get "https://localhost:9001/hello/world" default-options-for-https-client)]
325 (is (= (:status response) 200))
326 (is (= (:body response) "Hello, World!")))
327 (let [response (http-get "https://localhost:10001/hello-proxy/world" default-options-for-https-client)]
328 (is (= (:status response) 200))
329 (is (= (:body response) "Hello, World!"))))))
330
331 (deftest http-https-proxy-support
332 (testing "http->https proxy support with explicit ssl config for proxy"
333 (with-target-and-proxy-servers
334 {:target (merge common-ssl-config
335 {:ssl-host "0.0.0.0"
336 :ssl-port 9000})
337 :proxy {:host "0.0.0.0"
338 :port 10000}
339 :proxy-config {:host "localhost"
340 :port 9000
341 :path "/hello"}
342 :proxy-opts {:scheme :https
343 :ssl-config common-ssl-config}
344 :ring-handler proxy-ring-handler}
345 (let [response (http-get "https://localhost:9000/hello/world" default-options-for-https-client)]
346 (is (= (:status response) 200))
347 (is (= (:body response) "Hello, World!")))
348 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
349 (is (= (:status response) 200))
350 (is (= (:body response) "Hello, World!")))))
351
352 (testing "http->https proxy support with scheme as string value"
353 (with-target-and-proxy-servers
354 {:target (merge common-ssl-config
355 {:ssl-host "0.0.0.0"
356 :ssl-port 9000})
357 :proxy {:host "0.0.0.0"
358 :port 10000}
359 :proxy-config {:host "localhost"
360 :port 9000
361 :path "/hello"}
362 :proxy-opts {:scheme "https"
363 :ssl-config common-ssl-config}
364 :ring-handler proxy-ring-handler}
365 (let [response (http-get "https://localhost:9000/hello/world" default-options-for-https-client)]
366 (is (= (:status response) 200))
367 (is (= (:body response) "Hello, World!")))
368 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
369 (is (= (:status response) 200))
370 (is (= (:body response) "Hello, World!"))))))
371
372 (deftest https-http-proxy-support
373
374 (testing "https->http proxy support"
375 (with-target-and-proxy-servers
376 {:target {:host "0.0.0.0"
377 :port 9001}
378 :proxy (merge common-ssl-config
379 {:ssl-host "0.0.0.0"
380 :ssl-port 10001})
381 :proxy-config {:host "localhost"
382 :port 9001
383 :path "/hello"}
384 :proxy-opts {:scheme :http}
385 :ring-handler proxy-ring-handler}
386 (let [response (http-get "http://localhost:9001/hello/world")]
387 (is (= (:status response) 200))
388 (is (= (:body response) "Hello, World!")))
389 (let [response (http-get "https://localhost:10001/hello-proxy/world" default-options-for-https-client)]
390 (is (= (:status response) 200))
391 (is (= (:body response) "Hello, World!")))))
392
393 (testing "https->http proxy support with scheme as string"
394 (with-target-and-proxy-servers
395 {:target {:host "0.0.0.0"
396 :port 9001}
397 :proxy (merge common-ssl-config
398 {:ssl-host "0.0.0.0"
399 :ssl-port 10001})
400 :proxy-config {:host "localhost"
401 :port 9001
402 :path "/hello"}
403 :proxy-opts {:scheme "http"}
404 :ring-handler proxy-ring-handler}
405 (let [response (http-get "http://localhost:9001/hello/world")]
406 (is (= (:status response) 200))
407 (is (= (:body response) "Hello, World!")))
408 (let [response (http-get "https://localhost:10001/hello-proxy/world" default-options-for-https-client)]
409 (is (= (:status response) 200))
410 (is (= (:body response) "Hello, World!"))))))
411
412 (deftest proxy-support-with-callback
413 (testing "basic http proxy support with callback function"
414 (with-target-and-proxy-servers
415 {:target {:host "0.0.0.0"
416 :port 9000}
417 :proxy {:host "0.0.0.0"
418 :port 10000}
419 :proxy-config {:host "localhost"
420 :port 9000
421 :path "/hello"}
422 :proxy-opts {:callback-fn callback-fn}
423 :ring-handler proxy-ring-handler}
424 (let [response (http-get "http://localhost:9000/hello/world")]
425 (is (= (:status response) 200))
426 (is (= (:body response) "Hello, World!")))
427 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
428 (is (= (:status response) 200))
429 (is (= (:body response) "Hello, World!!!!")))))
430
431 (testing "basic https proxy support (pass-through https config) with callback function"
432 (with-target-and-proxy-servers
433 {:target (merge common-ssl-config
434 {:ssl-host "0.0.0.0"
435 :ssl-port 9001})
436 :proxy (merge common-ssl-config
437 {:ssl-host "0.0.0.0"
438 :ssl-port 10001})
439 :proxy-config {:host "localhost"
440 :port 9001
441 :path "/hello"}
442 :proxy-opts {:callback-fn callback-fn}
443 :ring-handler proxy-ring-handler}
444 (let [response (http-get "https://localhost:9001/hello/world" default-options-for-https-client)]
445 (is (= (:status response) 200))
446 (is (= (:body response) "Hello, World!")))
447 (let [response (http-get "https://localhost:10001/hello-proxy/world" default-options-for-https-client)]
448 (is (= (:status response) 200))
449 (is (= (:body response) "Hello, World!!!!")))))
450
451 (testing "http->https proxy support with explicit ssl config and callback function for proxy"
452 (with-target-and-proxy-servers
453 {:target (merge common-ssl-config
454 {:ssl-host "0.0.0.0"
455 :ssl-port 9000})
456 :proxy {:host "0.0.0.0"
457 :port 10000}
458 :proxy-config {:host "localhost"
459 :port 9000
460 :path "/hello"}
461 :proxy-opts {:scheme :https
462 :ssl-config common-ssl-config
463 :callback-fn callback-fn}
464 :ring-handler proxy-ring-handler}
465 (let [response (http-get "https://localhost:9000/hello/world" default-options-for-https-client)]
466 (is (= (:status response) 200))
467 (is (= (:body response) "Hello, World!")))
468 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
469 (is (= (:status response) 200))
470 (is (= (:body response) "Hello, World!!!!"))))))
471
472 (deftest proxy-with-rewrite-uri-callback
473 (testing "basic http proxy support with rewrite uri callback function"
474 (with-target-and-proxy-servers
475 {:target {:host "0.0.0.0"
476 :port 9000}
477 :proxy {:host "0.0.0.0"
478 :port 10000}
479 :proxy-config {:host "localhost"
480 :port 9000
481 :path "/hello"}
482 :proxy-opts {:rewrite-uri-callback-fn rewrite-uri-callback-fn}
483 :ring-handler proxy-ring-handler}
484 (let [response (http-get "http://localhost:9000/hello/world")]
485 (is (= (:status response) 200))
486 (is (= (:body response) "Hello, World!")))
487 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
488 (is (= (:status response) 200))
489 (is (= (:body response) "Hello, Earth!")))))
490
491 (testing "basic https proxy support (pass-through https config) with rewrite uri callback function"
492 (with-target-and-proxy-servers
493 {:target (merge common-ssl-config
494 {:ssl-host "0.0.0.0"
495 :ssl-port 9001})
496 :proxy (merge common-ssl-config
497 {:ssl-host "0.0.0.0"
498 :ssl-port 10001})
499 :proxy-config {:host "localhost"
500 :port 9001
501 :path "/hello"}
502 :proxy-opts {:rewrite-uri-callback-fn rewrite-uri-callback-fn}
503 :ring-handler proxy-ring-handler}
504 (let [response (http-get "https://localhost:9001/hello/world" default-options-for-https-client)]
505 (is (= (:status response) 200))
506 (is (= (:body response) "Hello, World!")))
507 (let [response (http-get "https://localhost:10001/hello-proxy/world" default-options-for-https-client)]
508 (is (= (:status response) 200))
509 (is (= (:body response) "Hello, Earth!")))))
510
511 (testing "http->https proxy support with explicit ssl config and rewrite uri callback function for proxy"
512 (with-target-and-proxy-servers
513 {:target (merge common-ssl-config
514 {:ssl-host "0.0.0.0"
515 :ssl-port 9000})
516 :proxy {:host "0.0.0.0"
517 :port 10000}
518 :proxy-config {:host "localhost"
519 :port 9000
520 :path "/hello"}
521 :proxy-opts {:scheme :https
522 :ssl-config common-ssl-config
523 :rewrite-uri-callback-fn rewrite-uri-callback-fn}
524 :ring-handler proxy-ring-handler}
525 (let [response (http-get "https://localhost:9000/hello/world" default-options-for-https-client)]
526 (is (= (:status response) 200))
527 (is (= (:body response) "Hello, World!")))
528 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
529 (is (= (:status response) 200))
530 (is (= (:body response) "Hello, Earth!"))))))
531
532 (deftest proxy-with-query-params
533 (testing "basic proxy support with query parameters"
534 (with-target-and-proxy-servers
535 {:target {:host "0.0.0.0"
536 :port 9000}
537 :proxy {:host "0.0.0.0"
538 :port 10000}
539 :proxy-config {:host "localhost"
540 :port 9000
541 :path "/hello"}
542 :ring-handler app-wrapped}
543 (let [response (http-get "http://localhost:9000/hello?foo=bar")]
544 (is (= (:status response) 200))
545 (is (= (:body response) (str {"foo" "bar"}))))
546 (let [response (http-get "http://localhost:10000/hello-proxy?foo=bar")]
547 (is (= (:status response) 200))
548 (is (= (:body response) (str {"foo" "bar"}))))))
549
550 (testing "basic proxy support with url encodable query parameters"
551 (with-target-and-proxy-servers
552 {:target {:host "0.0.0.0"
553 :port 9000}
554 :proxy {:host "0.0.0.0"
555 :port 10000}
556 :proxy-config {:host "localhost"
557 :port 9000
558 :path "/hello"}
559 :ring-handler app-wrapped}
560 (let [response (http-get "http://localhost:9000/hello?hello%5B%5D=hello%20world")]
561 (is (= (:status response) 200))
562 (is (= (:body response) (str {"hello[]" "hello world"}))))
563 (let [response (http-get "http://localhost:10000/hello-proxy?hello%5B%5D=hello%20world")]
564 (is (= (:status response) 200))
565 (is (= (:body response) (str {"hello[]" "hello world"}))))))
566
567 (testing "basic proxy support with multiple query parameters"
568 (let [params {"foo" "bar"
569 "baz" "lux"
570 "hello" "world"}
571 query "?foo=bar&baz=lux&hello=world"]
572 (with-target-and-proxy-servers
573 {:target {:host "0.0.0.0"
574 :port 9000}
575 :proxy {:host "0.0.0.0"
576 :port 10000}
577 :proxy-config {:host "localhost"
578 :port 9000
579 :path "/hello"}
580 :ring-handler app-wrapped}
581 (let [response (http-get (str "http://localhost:9000/hello" query))]
582 (is (= (:status response) 200))
583 (is (= (read-string (:body response)) params)))
584 (let [response (http-get (str "http://localhost:10000/hello-proxy" query))]
585 (is (= (:status response) 200))
586 (is (= (read-string (:body response)) params)))))))
587
588 (deftest proxy-with-redirect
589 (testing "redirect test with proxy"
590 (with-target-and-proxy-servers
591 {:target {:host "0.0.0.0"
592 :port 9000}
593 :proxy {:host "0.0.0.0"
594 :port 10000}
595 :proxy-config {:host "localhost"
596 :port 9000
597 :path "/hello"}
598 :proxy-opts {:follow-redirects true}
599 :ring-handler redirect-test-handler}
600 (let [response (http-get (str "http://localhost:9000/hello/"))]
601 (is (= (:status response) 200))
602 (is (= (:body response) "Hello, World!")))
603 (let [response (http-get (str "http://localhost:9000/hello/world"))]
604 (is (= (:status response) 200))
605 (is (= (:body response) "Hello, World!")))
606 (let [response (http-get (str "http://localhost:10000/hello-proxy"))]
607 (is (= (:status response) 200))
608 (is (= (:body response) "Hello, World!")))
609 (let [response (http-get (str "http://localhost:10000/hello-proxy/world"))]
610 (is (= (:status response) 200))
611 (is (= (:body response) "Hello, World!")))))
612
613 (testing "proxy redirect fails if :follow-redirects not configured properly"
614 (with-target-and-proxy-servers
615 {:target {:host "0.0.0.0"
616 :port 9000}
617 :proxy {:host "0.0.0.0"
618 :port 10000}
619 :proxy-config {:host "localhost"
620 :port 9000
621 :path "/hello"}
622 :ring-handler redirect-test-handler}
623 (let [response (http-get (str "http://localhost:10000/hello-proxy"))]
624 (is (= (:status response) 404)))))
625
626 (testing "proxy-redirect to non-target host fails"
627 (with-target-and-proxy-servers
628 {:target {:host "0.0.0.0"
629 :port 9000}
630 :proxy {:host "0.0.0.0"
631 :port 10000}
632 :proxy-config {:host "localhost"
633 :port 9000
634 :path "/hello"}
635 :proxy-opts {:follow-redirects true}
636 :ring-handler redirect-wrong-host}
637 (let [response (http-get (str "http://localhost:10000/hello-proxy"))]
638 (is (= (:status response) 502)))))
639
640 (testing "proxy redirect to correct host in fully qualified url works"
641 (with-target-and-proxy-servers
642 {:target {:host "0.0.0.0"
643 :port 9000}
644 :proxy {:host "0.0.0.0"
645 :port 10000}
646 :proxy-config {:host "localhost"
647 :port 9000
648 :path "/hello"}
649 :proxy-opts {:follow-redirects true}
650 :ring-handler redirect-same-host}
651 (let [response (http-get (str "http://localhost:9000/hello/"))]
652 (is (= (:status response) 200))
653 (is (= (:body response) "Hello, World!")))
654 (let [response (http-get (str "http://localhost:9000/hello/world"))]
655 (is (= (:status response) 200))
656 (is (= (:body response) "Hello, World!")))
657 (let [response (http-get (str "http://localhost:10000/hello-proxy"))]
658 (is (= (:status response) 200))
659 (is (= (:body response) "Hello, World!")))
660 (let [response (http-get (str "http://localhost:10000/hello-proxy/world"))]
661 (is (= (:status response) 200))
662 (is (= (:body response) "Hello, World!")))))
663
664 (testing "proxy-redirect to non-proxied path on correct host succeeds"
665 (with-target-and-proxy-servers
666 {:target {:host "0.0.0.0"
667 :port 9000}
668 :proxy {:host "0.0.0.0"
669 :port 10000}
670 :proxy-config {:host "localhost"
671 :port 9000
672 :path "/hello"}
673 :proxy-opts {:follow-redirects true}
674 :ring-handler redirect-different-proxy-path}
675 (let [response (http-get (str "http://localhost:9000/hello/"))]
676 (is (= (:status response) 200))
677 (is (= (:body response) "Hello, World!")))
678 (let [response (http-get (str "http://localhost:10000/hello-proxy"))]
679 (is (= (:status response) 200))
680 (is (= (:body response) "Hello, World!"))))))
681
682 (deftest proxy-failure
683 (testing "proxying failure - default handler"
684 (with-target-and-proxy-servers
685 {:target {:host "0.0.0.0"
686 :port 9000}
687 :proxy {:host "0.0.0.0"
688 :port 10000}
689 :proxy-config {:host "localhost"
690 :port 123456789 ; illegal port number
691 :path "/hello"}
692 :ring-handler proxy-ring-handler}
693 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
694 (is (= (:status response) 502))
695 (is (= (:body response) "")))))
696
697 (testing "proxying failure - custom handler"
698 (with-target-and-proxy-servers
699 {:target {:host "0.0.0.0"
700 :port 9000}
701 :proxy {:host "0.0.0.0"
702 :port 10000}
703 :proxy-config {:host "localhost"
704 :port 123456789 ; illegal port number
705 :path "/hello"}
706 :proxy-opts {:failure-callback-fn failure-callback-fn}
707 :ring-handler proxy-ring-handler}
708 (let [response (http-get "http://localhost:10000/hello-proxy/world")]
709 (is (= (:status response) 500))
710 (is (= (:body response) "Proxying failed: port out of range:123456789")))))
711
712 (testing "setting an idle timeout fails properly"
713 (with-target-and-proxy-servers
714 {:target {:host "0.0.0.0"
715 :port 9000}
716 :proxy {:host "0.0.0.0"
717 :port 10000}
718 :proxy-config {:host "localhost"
719 :port 9000
720 :path "/hello"}
721 :proxy-opts {:idle-timeout 1}
722 :ring-handler (ring-handler-with-sleep 1250)}
723 (let [response (http-get (str "http://localhost:10000/hello-proxy"))]
724 (is (= 504 (:status response))))))
725
726 (testing "a response before a timeout occurs succeeds"
727 (with-target-and-proxy-servers
728 {:target {:host "0.0.0.0"
729 :port 9000}
730 :proxy {:host "0.0.0.0"
731 :port 10000}
732 :proxy-config {:host "localhost"
733 :port 9000
734 :path "/hello"}
735 :proxy-opts {:idle-timeout 1}
736 :ring-handler (ring-handler-with-sleep 100)}
737 (let [response (http-get (str "http://localhost:10000/hello-proxy"))]
738 (is (= 200 (:status response)))))))
739
740 ; Modified from proxy-ring-handler
741 (defn count-ring-handler
742 "Increments counter if the endpoint is /goodbye"
743 [counter req]
744 (condp = (:uri req)
745 "/hello/world/" {:status 200 :body (str "Hello, World!")}
746 "/hello/" {:status 200 :body (str "Hello, You!")}
747 "/goodbye/" {:status 200 :body (str "Goodbye! Count: " (swap! counter inc))}
748 {:status 404 :body "count-ring-handler couldn't find the route"}))
749
750 (deftest test-path-traversal-attacks
751 (testing "proxies aren't vulnerable to basic path traversal attacks"
752 ; goodbye-counter is used to make sure that no requests to the proxy
753 ; endpoint, /hello-proxy, end up hitting /goodbye, as they should only be
754 ; able to reach /hello
755 (let [goodbye-counter (atom 0)
756 hello-goodbye-count-ring-handler (partial count-ring-handler goodbye-counter)
757 ; Should not succeed in hitting the goodbye endpoint
758 bad-proxy-requests [; Encodings of '../'
759 "https://localhost:10001/hello-proxy/../goodbye/"
760 "https://localhost:10001/hello-proxy/%2e%2e%2fgoodbye/"
761 "https://localhost:10001/hello-proxy/%2e%2e/goodbye/"
762 "https://localhost:10001/hello-proxy/..%2fgoodbye/"
763 ; Encodings of '../../'
764 "https://localhost:10001/hello-proxy/world/../../goodbye/"
765 "https://localhost:10001/hello-proxy/world/%2e%2e%2f%2e%2e%2fgoodbye/"
766 "https://localhost:10001/hello-proxy/world/%2e%2e/%2e%2e/goodbye/"
767 "https://localhost:10001/hello-proxy/world/..%2f..%2fgoodbye/"]]
768 (with-target-and-proxy-servers
769 {:target (merge common-ssl-config
770 {:ssl-host "0.0.0.0"
771 :ssl-port 9001})
772 :proxy (merge common-ssl-config
773 {:ssl-host "0.0.0.0"
774 :ssl-port 10001})
775 :proxy-config {:host "localhost"
776 :port 9001
777 :path "/hello"}
778 :ring-handler hello-goodbye-count-ring-handler}
779 (testing "proxy is up and running"
780 (let [response (http-get "https://localhost:10001/hello-proxy/" default-options-for-https-client)]
781 (is (= 200 (:status response)))
782 (is (= "Hello, You!" (:body response))))
783 (let [response (http-get "https://localhost:10001/hello-proxy/world/" default-options-for-https-client)]
784 (is (= 200 (:status response)))
785 (is (= "Hello, World!" (:body response)))))
786 (testing "non-proxied endpoint doesn't see any traffic"
787 (doall (for [bad-request bad-proxy-requests]
788 (let [response (http-get bad-request default-options-for-https-client)]
789 (is (= 404 (:status response)))
790 ; Make sure the 404 is because jetty couldn't find the page, not
791 ; because the ring handler didn't understand the request, which
792 ; would have a different message in the body. The request shouldn't
793 ; get as far as the hello-goodbye-count-ring-handler
794 (is (re-find #"Problem accessing /hello-proxy" (:body response))))))
795 ; Counter should still be at 0
796 (is (= 0 (deref goodbye-counter))))
797 (testing "counter is working correctly"
798 ; A valid request to bump the counter
799 (http-get "https://localhost:9001/goodbye/" default-options-for-https-client)
800 (is (= 1 (deref goodbye-counter))))))))
0 (ns puppetlabs.trapperkeeper.services.webserver.jetty9-service-test
1 (:import (org.apache.http ConnectionClosedException)
2 (java.io IOException)
3 (java.security.cert CRLException)
4 (java.net BindException)
5 (java.nio.file Paths Files)
6 (java.nio.file.attribute FileAttribute)
7 (appender TestListAppender))
8 (:require [clojure.test :refer :all]
9 [puppetlabs.http.client.async :as async]
10 [puppetlabs.http.client.common :as http-client-common]
11 [puppetlabs.kitchensink.testutils.fixtures :as ks-test-fixtures]
12 [puppetlabs.trapperkeeper.app :as tk-app]
13 [puppetlabs.trapperkeeper.core :as tk-core]
14 [puppetlabs.trapperkeeper.services :as tk-services]
15 [puppetlabs.trapperkeeper.services.webserver.jetty9-service
16 :refer :all]
17 [puppetlabs.trapperkeeper.testutils.webserver :as testutils]
18 [puppetlabs.trapperkeeper.testutils.webserver.common :refer :all]
19 [puppetlabs.trapperkeeper.testutils.bootstrap
20 :refer [with-app-with-empty-config
21 with-app-with-config]
22 :as tk-bootstrap]
23 [puppetlabs.trapperkeeper.logging :as tk-log]
24 [puppetlabs.trapperkeeper.testutils.logging :as tk-log-testutils]
25 [puppetlabs.trapperkeeper.services.webserver.jetty9-core :as core]
26 [schema.core :as schema]
27 [schema.test :as schema-test]))
28
29 (use-fixtures :once
30 ks-test-fixtures/with-no-jvm-shutdown-hooks
31 schema-test/validate-schemas
32 testutils/assert-clean-shutdown
33 (fn [f] (tk-log/reset-logging) (f)))
34
35 (def default-server-config
36 {:webserver {:foo {:port 8080}
37 :bar {:port 9000
38 :default-server true}}})
39
40 (def no-default-config
41 {:webserver {:foo {:port 8080}
42 :bar {:port 9000}}})
43
44 (def static-content-single-config
45 {:webserver {:port 8080
46 :static-content [{:resource "./dev-resources"
47 :path "/resources"
48 :follow-links true},
49 {:resource "./dev-resources"
50 :path "/resources2"}]}})
51
52 (def static-content-multi-config
53 {:webserver {:foo {:port 8080
54 :static-content [{:resource "./dev-resources"
55 :path "/resources"},
56 {:resource "./dev-resources"
57 :path "/resources2"}]}
58 :bar {:port 9000
59 :static-content [{:resource "./dev-resources"
60 :path "/resources"},
61 {:resource "./dev-resources"
62 :path "/resources2"}]}}})
63
64 (defmacro ssl-exception-thrown?
65 [& body]
66 `(try
67 ~@body
68 (throw (IllegalStateException. "Expected SSL Exception to be thrown!"))
69 (catch ConnectionClosedException e#
70 true)
71 (catch IOException e#
72 (if (= "Connection reset by peer" (.getMessage e#))
73 true
74 (throw e#)))))
75
76 (def unauthorized-pem-options-for-https
77 (-> default-options-for-https-client
78 (assoc :ssl-cert "./dev-resources/config/jetty/ssl/certs/unauthorized.pem")
79 (assoc :ssl-key "./dev-resources/config/jetty/ssl/private_keys/unauthorized.pem")))
80
81 (def hello-path "/hi_world")
82 (def hello-body "Hi World")
83 (defn hello-handler
84 [req]
85 {:status 200 :body hello-body})
86
87 (tk-core/defservice hello-webservice
88 [[:WebserverService add-ring-handler]]
89 (init [this context]
90 (add-ring-handler hello-handler hello-path)
91 context))
92
93 (defn validate-ring-handler
94 ([base-url config]
95 (validate-ring-handler base-url config {:as :text} :default))
96 ([base-url config http-get-options]
97 (validate-ring-handler base-url config http-get-options :default))
98 ([base-url config http-get-options server-id]
99 (with-app-with-config app
100 [jetty9-service
101 hello-webservice]
102 config
103 (let [response (http-get
104 (format "%s%s/" base-url hello-path)
105 http-get-options)]
106 (is (= (:status response) 200))
107 (is (= (:body response) hello-body))))))
108
109 (defn validate-ring-handler-default
110 ([base-url config]
111 (validate-ring-handler-default base-url config {:as :text}))
112 ([base-url config http-get-options]
113 (with-app-with-config app
114 [jetty9-service
115 hello-webservice]
116 config
117 (let [response (http-get
118 (format "%s%s/" base-url hello-path)
119 http-get-options)]
120 (is (= (:status response) 200))
121 (is (= (:body response) hello-body))))))
122
123 (deftest basic-ring-test
124 (testing "ring request over http succeeds"
125 (validate-ring-handler
126 "http://localhost:8080"
127 jetty-plaintext-config)))
128
129 (deftest basic-default-ring-test
130 (testing "ring request over http succeeds with default add-ring-handler"
131 (validate-ring-handler-default
132 "http://localhost:8080"
133 jetty-plaintext-config))
134 (testing "ring request on single server with new syntax over http succeeds"
135 (validate-ring-handler
136 "http://localhost:8080"
137 {:webserver {:default {:port 8080
138 :default-server true}}}
139 {:as :text}
140 :default)))
141
142 (deftest single-server-jmx-cleanup-test
143 (testing "no jetty mbeancontainers are registered prior to starting servers"
144 (is (empty? (testutils/get-jetty-mbean-object-names))))
145 (with-app-with-config app
146 [jetty9-service]
147 jetty-plaintext-config
148 (testing "one jetty mbean container is registered per server"
149 (is (= 1 (count (testutils/get-jetty-mbean-object-names))))))
150 (testing "jetty mbean containers are unregistered after server is stopped"
151 (is (empty? (testutils/get-jetty-mbean-object-names)))))
152
153 (deftest multiserver-ring-test
154 (testing "no jetty mbeancontainers are registered prior to starting servers"
155 (is (empty? (testutils/get-jetty-mbean-object-names))))
156
157 (testing "ring requests on multiple servers succeed"
158 (with-app-with-config app
159 [jetty9-service]
160 jetty-multiserver-plaintext-config
161 (let [s (tk-app/get-service app :WebserverService)
162 add-ring-handler (partial add-ring-handler s)]
163 (add-ring-handler hello-handler hello-path {:server-id :foo})
164 (add-ring-handler hello-handler hello-path {:server-id :bar})
165 (let [response1 (http-get "http://localhost:8080/hi_world/" {:as :text})
166 response2 (http-get "http://localhost:8085/hi_world/" {:as :text})]
167 (is (= (:status response1) 200))
168 (is (= (:status response2) 200))
169 (is (= (:body response1) hello-body))
170 (is (= (:body response2) hello-body)))
171
172 (testing "one jetty mbean container is registered per server"
173 (is (= 2 (count (testutils/get-jetty-mbean-object-names))))))))
174
175 (testing "jetty mbean containers are unregistered after server is stopped"
176 (is (empty? (testutils/get-jetty-mbean-object-names))))
177
178 (testing "ring request succeeds with multiple servers and default add-ring-handler"
179 (validate-ring-handler-default
180 "http://localhost:8080"
181 jetty-multiserver-plaintext-config)))
182
183 (deftest port-test
184 (testing "webserver bootstrap throws IllegalArgumentException when neither
185 port nor ssl-port specified in the config"
186 (is (thrown-with-msg?
187 IllegalArgumentException
188 #"Either host, port, ssl-host, or ssl-port must be specified"
189 (tk-log-testutils/with-test-logging
190 (with-app-with-empty-config app [jetty9-service])))
191 "Did not encounter expected exception when no port specified in config")))
192
193 (deftest ssl-success-test
194 (testing "ring request over SSL successful for both .jks and .pem
195 implementations with the server's client-auth setting not set and
196 the client configured provide a certificate which the CA can
197 validate"
198 ; Note that if the 'client-auth' setting is not set that the server
199 ; should default to 'need' to validate the client certificate. In this
200 ; case, the validation should be successful because the client is
201 ; providing a certificate which the CA can validate.
202 (doseq [config [jetty-ssl-jks-config jetty-ssl-pem-config]]
203 (validate-ring-handler
204 "https://localhost:8081"
205 config
206 default-options-for-https-client)))
207
208 (testing "ring request over SSL succeeds with a server client-auth setting
209 of 'need' and the client configured to provide a certificate which
210 the CA can validate"
211 (validate-ring-handler
212 "https://localhost:8081"
213 jetty-ssl-client-need-config
214 default-options-for-https-client))
215
216 (testing "ring request over SSL succeeds with a server client-auth setting
217 of 'want' and the client configured to provide a certificate which
218 the CA can validate"
219 (validate-ring-handler
220 "https://localhost:8081"
221 jetty-ssl-client-want-config
222 default-options-for-https-client))
223
224 (testing "ring request over SSL succeeds with a server client-auth setting
225 of 'want' and the client configured to not provide a certificate"
226 (validate-ring-handler
227 "https://localhost:8081"
228 jetty-ssl-client-want-config
229 (dissoc default-options-for-https-client :ssl-cert :ssl-key)))
230
231 (testing "ring request over SSL succeeds with a server client-auth setting
232 of 'none' and the client configured to provide a certificate which
233 the CA can validate"
234 (validate-ring-handler
235 "https://localhost:8081"
236 jetty-ssl-client-none-config
237 default-options-for-https-client))
238
239 (testing "ring request over SSL succeeds with a server client-auth setting
240 of 'none' and the client configured to not provide a certificate"
241 (validate-ring-handler
242 "https://localhost:8081"
243 jetty-ssl-client-none-config
244 (dissoc default-options-for-https-client :ssl-cert :ssl-key)))
245
246 (testing "ring request over SSL succeeds with a server client-auth setting
247 of 'none' and the client configured to provide a certificate which
248 the CA cannot validate"
249 (validate-ring-handler
250 "https://localhost:8081"
251 jetty-ssl-client-none-config
252 unauthorized-pem-options-for-https))
253
254 (testing "ring request over SSL succeeds with the server configured to use
255 both an ssl-cert and an ssl-cert-chain"
256 (validate-ring-handler
257 "https://localhost:8081"
258 (assoc-in jetty-ssl-client-need-config
259 [:webserver :ssl-cert-chain]
260 (:ssl-ca-cert default-options-for-https-client))
261 default-options-for-https-client)))
262
263 (deftest ssl-failure-test
264 (testing "ring request over SSL fails with the server's client-auth setting
265 not set and the client configured to provide a certificate which
266 the CA cannot validate"
267 ; Note that if the 'client-auth' setting is not set that the server
268 ; should default to 'need' to validate the client certificate. In this
269 ; case, the validation should fail because the client is providing a
270 ; certificate which the CA cannot validate.
271 (is (ssl-exception-thrown?
272 (validate-ring-handler
273 "https://localhost:8081"
274 jetty-ssl-pem-config
275 unauthorized-pem-options-for-https))))
276
277 (testing "ring request over SSL fails with the server's client-auth setting
278 not set and the client configured to not provide a certificate"
279 ; Note that if the 'client-auth' setting is not set that the server
280 ; should default to 'need' to validate the client certificate. In this
281 ; case, the validation should fail because the client is not providing a
282 ; certificate
283 (is (ssl-exception-thrown?
284 (validate-ring-handler
285 "https://localhost:8081"
286 jetty-ssl-pem-config
287 (dissoc default-options-for-https-client :ssl-cert :ssl-key)))))
288
289 (testing "ring request over SSL fails with a server client-auth setting
290 of 'need' and the client configured to provide a certificate which
291 the CA cannot validate"
292 (is (ssl-exception-thrown?
293 (validate-ring-handler
294 "https://localhost:8081"
295 jetty-ssl-client-need-config
296 unauthorized-pem-options-for-https))))
297
298 (testing "ring request over SSL fails with a server client-auth setting
299 of 'need' and the client configured to not provide a certificate"
300 (is (ssl-exception-thrown?
301 (validate-ring-handler
302 "https://localhost:8081"
303 jetty-ssl-client-need-config
304 (dissoc default-options-for-https-client :ssl-cert :ssl-key)))))
305
306 (testing "ring request over SSL fails with a server client-auth setting
307 of 'want' and the client configured to provide a certificate which
308 the CA cannot validate"
309 (is (ssl-exception-thrown?
310 (validate-ring-handler
311 "https://localhost:8081"
312 jetty-ssl-client-want-config
313 unauthorized-pem-options-for-https)))))
314
315 (deftest crl-success-test
316 (testing (str "ring request over SSL succeeds when no client certificates "
317 "have been revoked")
318 (validate-ring-handler
319 "https://localhost:8081"
320 (assoc-in jetty-ssl-client-need-config
321 [:webserver :ssl-crl-path]
322 "./dev-resources/config/jetty/ssl/crls/crls_none_revoked.pem")
323 default-options-for-https-client))
324
325 (testing (str "ring request over SSL succeeds when a different client "
326 "certificate than the one used in the request has been revoked")
327 (validate-ring-handler
328 "https://localhost:8081"
329 (assoc-in jetty-ssl-client-need-config
330 [:webserver :ssl-crl-path]
331 (str "./dev-resources/config/jetty/ssl/crls/"
332 "crls_localhost-compromised_revoked.pem"))
333 default-options-for-https-client)))
334
335 (deftest crl-failure-test
336 (testing (str "ring request over SSL fails when the client certificate has "
337 "been revoked")
338 (is (ssl-exception-thrown?
339 (validate-ring-handler
340 "https://localhost:8081"
341 (assoc-in
342 jetty-ssl-client-need-config
343 [:webserver :ssl-crl-path]
344 "./dev-resources/config/jetty/ssl/crls/crls_localhost_revoked.pem")
345 default-options-for-https-client))))
346
347 (testing (str "jetty throws startup exception if non-CRL PEM is specified "
348 "as ssl-crl-path")
349 (tk-log-testutils/with-test-logging
350 (is (thrown?
351 CRLException
352 (with-app-with-config
353 app
354 [jetty9-service]
355 (assoc-in
356 jetty-ssl-client-need-config
357 [:webserver :ssl-crl-path]
358 "./dev-resources/config/jetty/ssl/certs/ca.pem"))))))
359
360 (testing (str "jetty throws startup exception if ssl-crl-path refers to a "
361 "non-existent file")
362 (tk-log-testutils/with-test-logging
363 (is (thrown-with-msg?
364 IllegalArgumentException
365 #"Non-readable path specified for ssl-crl-path option"
366 (with-app-with-config
367 app
368 [jetty9-service]
369 (assoc-in
370 jetty-ssl-client-need-config
371 [:webserver :ssl-crl-path]
372 "./dev-resources/config/jetty/ssl/crls/crls_bogus.pem")))))))
373
374 (defn boot-service-and-jetty-with-default-config
375 [service]
376 (tk-core/boot-services-with-config
377 [service jetty9-service]
378 jetty-plaintext-config))
379
380 (defn boot-service-and-jetty-with-multiserver-config
381 [service]
382 (tk-core/boot-services-with-config
383 [service jetty9-service]
384 jetty-multiserver-plaintext-config))
385
386 (defn get-jetty-server-from-app-context
387 ([app]
388 (get-jetty-server-from-app-context app :default))
389 ([app server-id]
390 (-> (tk-app/get-service app :WebserverService)
391 (tk-services/service-context)
392 (:jetty9-servers)
393 (server-id)
394 (:server))))
395
396 (deftest jetty-and-dependent-service-shutdown-after-service-error
397 (testing (str "jetty and any dependent services are shutdown after a"
398 "service throws an error from its start function")
399 (tk-log-testutils/with-test-logging
400 (let [shutdown-called? (atom false)
401 test-service (tk-services/service
402 [[:WebserverService]]
403 (start [this context]
404 (throw (Throwable. "oops"))
405 context)
406 (stop [this context]
407 (reset! shutdown-called? true)
408 context))
409 app (boot-service-and-jetty-with-default-config
410 test-service)
411 jetty-server (get-jetty-server-from-app-context app)]
412 (is (.isStarted jetty-server)
413 "Jetty server was never started before call to run-app")
414 (is (not (.isStopped jetty-server))
415 "Jetty server was stopped before call to run-app")
416 (is (thrown-with-msg?
417 Throwable
418 #"oops"
419 (tk-core/run-app app))
420 "tk run-app did not die with expected exception.")
421 (is (true? @shutdown-called?)
422 "Service shutdown was not called.")
423 (is (.isStopped jetty-server)
424 "Jetty server was not stopped after call to run-app."))))
425 (testing (str "jetty and any dependent services are shutdown after a"
426 "service throws an error from its start function"
427 "in a multi-server set-up")
428 (tk-log-testutils/with-test-logging
429 (let [shutdown-called? (atom false)
430 test-service (tk-services/service
431 [[:WebserverService]]
432 (start [this context]
433 (throw (Throwable. "oops"))
434 context)
435 (stop [this context]
436 (reset! shutdown-called? true)
437 context))
438 app (boot-service-and-jetty-with-multiserver-config
439 test-service)
440 jetty-server1 (get-jetty-server-from-app-context app :foo)
441 jetty-server2 (get-jetty-server-from-app-context app :bar)]
442 (is (.isStarted jetty-server1)
443 "First Jetty server was never started before call to run-app")
444 (is (.isStarted jetty-server2)
445 "Second Jetty server was never started before call to run-app")
446 (is (not (.isStopped jetty-server1))
447 "First Jetty server was stopped before call to run-app")
448 (is (not (.isStopped jetty-server2))
449 "Second Jetty server was stopped before call to run-app")
450 (is (thrown-with-msg?
451 Throwable
452 #"oops"
453 (tk-core/run-app app))
454 "tk run-app did not die with expected exception.")
455 (is (true? @shutdown-called?)
456 "Service shutdown was not called.")
457 (is (.isStopped jetty-server1)
458 "First Jetty server was not stopped after call to run-app.")
459 (is (.isStopped jetty-server2)
460 "Second Jetty server was not stopped after call to run-app."))))
461 (testing (str "jetty server instance never attached to the service context "
462 "and dependent services are shutdown after a service throws "
463 "an error from its init function")
464 (tk-log-testutils/with-test-logging
465 (let [shutdown-called? (atom false)
466 test-service (tk-services/service
467 [[:WebserverService]]
468 (init [this context]
469 (throw (Throwable. "oops"))
470 context)
471 (stop [this context]
472 (reset! shutdown-called? true)
473 context))
474 app (boot-service-and-jetty-with-default-config
475 test-service)
476 jetty-server (get-jetty-server-from-app-context app)]
477 (is (thrown-with-msg?
478 Throwable
479 #"oops"
480 (tk-core/run-app app))
481 "tk run-app did not die with expected exception.")
482 (is (nil? jetty-server)
483 (str "Jetty server was unexpectedly attached to the service "
484 "context."))
485 (is (true? @shutdown-called?)
486 "Service shutdown was not called."))))
487 (testing (str "attempt to launch second jetty server on same port as "
488 "already running jetty server fails with BindException without "
489 "placing second jetty server instance on app context")
490 (tk-log-testutils/with-test-logging
491 (let [first-app (tk-core/boot-services-with-config
492 [jetty9-service]
493 jetty-plaintext-config)]
494 (try
495 (let [second-app (tk-core/boot-services-with-config
496 [jetty9-service]
497 jetty-plaintext-config)
498 second-jetty-server (get-jetty-server-from-app-context
499 second-app)]
500 (is (logged?
501 #"^Encountered error starting web server, so shutting down")
502 "Didn't find log message for port bind error")
503 (is (nil? second-jetty-server)
504 "Jetty server was unexpectedly attached to the service context")
505 (is (thrown?
506 BindException
507 (tk-core/run-app second-app))
508 "tk run-app did not die with expected exception."))
509 (finally
510 (tk-app/stop first-app)))))))
511
512 (deftest default-server-test
513 (testing (str "specifying a config in the old format will start a server with "
514 "a server-id of :default")
515 (let [app (tk-core/boot-services-with-config
516 [jetty9-service]
517 jetty-plaintext-config)
518 context-list (-> (tk-app/get-service app :WebserverService)
519 (tk-services/service-context)
520 (:jetty9-servers))
521 jetty-server (get-jetty-server-from-app-context app)]
522 (is (contains? context-list :default)
523 "the default key was not added to the context list")
524 (is (nil? (schema/check core/ServerContext (:default context-list)))
525 "the value of the default key is not a valid server context")
526 (is (.isStarted jetty-server)
527 "the default server was never started")
528 (tk-app/stop app))))
529
530 (deftest large-request-test
531 (testing (str "request to Jetty fails with a 413 error if the request header "
532 "is too large and a larger one is not set")
533 (with-app-with-config app
534 [jetty9-service
535 hello-webservice]
536 jetty-plaintext-config
537 (tk-log-testutils/with-test-logging
538 (let [response (http-get "http://localhost:8080/hi_world" {:headers {"Cookie" absurdly-large-cookie}
539 :as :text})]
540 (is (= (:status response) 413))))))
541
542 (testing (str "request to Jetty succeeds with a large cookie if the request header "
543 "size is properly set")
544 (with-app-with-config app
545 [jetty9-service]
546 jetty-plaintext-large-request-config
547 (let [s (tk-app/get-service app :WebserverService)
548 add-ring-handler (partial add-ring-handler s)
549 body "Hi World"
550 path "/hi_world"
551 ring-handler (fn [req] {:status 200 :body (str body ((:headers req) "cookie"))})]
552 (add-ring-handler ring-handler path)
553 (let [response (http-get "http://localhost:8080/hi_world" {:headers {"Cookie" absurdly-large-cookie}
554 :as :text})]
555 (is (= (:status response) 200))
556 (is (= (:body response) (str "Hi World" absurdly-large-cookie))))))))
557
558 (deftest default-server-test
559 (testing "handler added to user-specified default server if no server-id is given"
560 (with-app-with-config app
561 [jetty9-service
562 hello-webservice]
563 default-server-config
564 (let [response (http-get "http://localhost:9000/hi_world")]
565 (is (= 200 (:status response)))
566 (is (= (:body response) hello-body)))))
567
568 (testing (str "exception thrown if user does not specify a "
569 "default server and no server-id is given")
570 (with-app-with-config app
571 [jetty9-service]
572 no-default-config
573 (let [s (tk-app/get-service app :WebserverService)
574 add-ring-handler (partial add-ring-handler s)]
575 (is (thrown? IllegalArgumentException
576 (add-ring-handler hello-handler hello-path)))))))
577
578 (deftest static-content-config-test
579 (let [logback (slurp "./dev-resources/logback.xml")]
580 (testing "static content can be specified in a single-server configuration"
581 (with-app-with-config
582 app
583 [jetty9-service]
584 static-content-single-config
585 (let [response (http-get "http://localhost:8080/resources/logback.xml")
586 response2 (http-get "http://localhost:8080/resources2/logback.xml")]
587 (is (= (:status response) 200))
588 (is (= (:body response) logback))
589 (is (= (:status response2) 200))
590 (is (= (:body response2) logback)))))
591
592 (testing "static content can be specified in a multi-server configuration"
593 (with-app-with-config
594 app
595 [jetty9-service]
596 static-content-multi-config
597 (let [response (http-get "http://localhost:8080/resources/logback.xml")
598 response2 (http-get "http://localhost:8080/resources2/logback.xml")]
599 (is (= (:status response) 200))
600 (is (= (:body response) logback))
601 (is (= (:status response2) 200))
602 (is (= (:body response2) logback)))
603 (let [response (http-get "http://localhost:9000/resources/logback.xml")
604 response2 (http-get "http://localhost:9000/resources2/logback.xml")]
605 (is (= (:status response) 200))
606 (is (= (:body response) logback))
607 (is (= (:status response2) 200))
608 (is (= (:body response2) logback)))))))
609
610 (deftest static-content-symlink-test
611 (let [logback (slurp (str dev-resources-dir "logback.xml"))
612 link (Paths/get (str dev-resources-dir "logback-link.xml") (into-array java.lang.String []))
613 file (Paths/get "logback.xml" (into-array java.lang.String []))]
614 (try
615 (Files/createSymbolicLink link file (into-array FileAttribute []))
616
617 (testing "static content can be served with symlinks when option specified in config"
618 (with-app-with-config
619 app
620 [jetty9-service]
621 static-content-single-config
622 (let [response (http-get "http://localhost:8080/resources/logback.xml")
623 response2 (http-get "http://localhost:8080/resources/logback-link.xml")]
624 (is (= (:status response) 200))
625 (is (= (:body response) logback))
626 (is (= (:status response2) 200))
627 (is (= (:body response2) logback)))))
628
629 (testing "static content cannot be served with symlinks if option not set"
630 (with-app-with-config
631 app
632 [jetty9-service]
633 static-content-single-config
634 (let [response (http-get "http://localhost:8080/resources2/logback.xml")
635 response2 (http-get "http://localhost:8080/resources2/logback-link.xml")]
636 (is (= (:status response) 200))
637 (is (= (:body response) logback))
638 (is (= (:status response2) 404)))))
639
640 (finally
641 (Files/delete link)))))
642
643 (deftest request-logging-test
644 (try
645 (testing "request logging occurs when :access-log-config is configured"
646 (with-app-with-config
647 app
648 [jetty9-service
649 hello-webservice]
650 {:webserver {:port 8080
651 :access-log-config
652 "./dev-resources/puppetlabs/trapperkeeper/services/webserver/request-logging.xml"}}
653 (http-get "http://localhost:8080/hi_world/")
654 ; Logging is done in a separate thread from Jetty and this test. As a result,
655 ; we have to sleep the thread to avoid a race condition.
656 (Thread/sleep 10)
657 (let [list (TestListAppender/list)]
658 (is (re-find #"\"GET /hi_world/ HTTP/1.1\" 200 8\n" (first list))))))
659 (finally
660 (.clear (TestListAppender/list)))))
661
662 (deftest graceful-shutdown-test
663 (testing "jetty9 webservers shut down gracefully by default"
664 (with-app-with-config
665 app
666 [jetty9-service]
667 jetty-plaintext-config
668 (let [s (tk-app/get-service app :WebserverService)
669 add-ring-handler (partial add-ring-handler s)
670 in-request-handler (promise)
671 ring-handler (fn [_]
672 (deliver in-request-handler true)
673 (Thread/sleep 3000)
674 {:status 200 :body "Hello, World!"})]
675 (add-ring-handler ring-handler "/hello")
676 (with-open [async-client (async/create-client {})]
677 (let [response (http-client-common/get async-client "http://localhost:8080/hello" {:as :text})]
678 @in-request-handler
679 (tk-app/stop app)
680 (is (= (:status @response) 200))
681 (is (= (:body @response) "Hello, World!")))))))
682
683 (testing "jetty9's stop timeout can be changed from config"
684 (with-app-with-config
685 app
686 [jetty9-service]
687 {:webserver {:port 8080 :shutdown-timeout-seconds 1}}
688 (let [s (tk-app/get-service app :WebserverService)
689 add-ring-handler (partial add-ring-handler s)
690 in-request-handler (promise)
691 ring-handler (fn [_]
692 (deliver in-request-handler true)
693 (Thread/sleep 2000)
694 {:status 200 :body "Hello, World!"})]
695 (add-ring-handler ring-handler "/hello")
696 (with-open [async-client (async/create-client {})]
697 (let [response (http-client-common/get async-client "http://localhost:8080/hello" {:as :text})]
698 @in-request-handler
699 (tk-log-testutils/with-test-logging
700 (tk-app/stop app))
701 (is (not (nil? (:error @response)))))))))
702
703 (testing "no graceful shutdown when stop timeout is set to 0"
704 (tk-log-testutils/with-test-logging
705 (with-app-with-config
706 app
707 [jetty9-service]
708 {:webserver {:port 8080 :shutdown-timeout-seconds 0}}
709 (let [s (tk-app/get-service app :WebserverService)
710 add-ring-handler (partial add-ring-handler s)
711 in-request-handler (promise)
712 ring-handler (fn [_]
713 (deliver in-request-handler true)
714 (Thread/sleep 300)
715 {:status 200 :body "Hello, World!"})]
716 (add-ring-handler ring-handler "/hello")
717 (with-open [async-client (async/create-client {})]
718 (let [response (http-client-common/get async-client "http://localhost:8080/hello" {:as :text})]
719 @in-request-handler
720 (tk-app/stop app)
721 (is (not (nil? (:error @response))))))))))
722
723 (testing "tk app can still restart even if stop timeout expires"
724 (let [in-request-handler? (promise)
725 unblock-request? (promise)
726
727 sleepy-service (tk-core/service
728 [[:WebserverService add-ring-handler]]
729 (init [this context]
730 (add-ring-handler
731 (fn [_]
732 (deliver in-request-handler? true)
733 @unblock-request?
734 {:status 200 :body hello-body})
735 hello-path)
736 context))]
737 (tk-log-testutils/with-test-logging
738 (with-app-with-config
739 app
740 [jetty9-service
741 sleepy-service]
742 {:webserver {:port 8080 :shutdown-timeout-seconds 1}}
743 (with-open [async-client (async/create-client {})]
744 (let [response (http-client-common/get async-client "http://localhost:8080/hi_world" {:as :text})]
745 @in-request-handler?
746 (tk-app/restart app)
747 (is (not (nil? (:error @response)))))
748 (deliver unblock-request? true)
749 (let [response (http-client-common/get async-client "http://localhost:8080/hi_world" {:as :text})]
750 (is (= 200 (:status @response)))
751 (is (= hello-body (:body @response))))))))))
752
753 (deftest double-stop-test
754 (testing "if the stop lifecycle is called more than once, we handle that gracefully and quietly"
755 (tk-log-testutils/with-logged-event-maps log-events
756 (let [app (tk-bootstrap/bootstrap-services-with-config
757 [jetty9-service]
758 {:webserver {:port 8080}})]
759 (tk-app/stop app)
760 (tk-app/stop app))
761 ;; we previously had a bug where we could try to unregister mbeans multiple
762 ;; times if our tk-j9 stop lifecycle function was called more than once.
763 ;; in that case, Jetty would log tons of nasty exceptions at warning level.
764 ;; this test just validates that that is not happening.
765 (let [log-event-filter #(or (= :warn (:level %))
766 (= :error (:level %)))
767 mbean-err-logs (filter log-event-filter @log-events)]
768 (is (= 0 (count mbean-err-logs)))))))
769
770 (deftest warn-if-sslv3-supported-test
771 (letfn [(start-server [ssl-protocols]
772 (let [config (if ssl-protocols
773 (assoc-in jetty-ssl-pem-config [:webserver :ssl-protocols] ssl-protocols)
774 jetty-ssl-pem-config)]
775 (with-app-with-config
776 app
777 [jetty9-service]
778 config)))]
779 (testing "warns if SSLv3 is in the protocol list"
780 (tk-log-testutils/with-test-logging
781 (start-server ["SSLv3" "TLSv1"])
782 (is (logged? #"known vulnerabilities"))))
783 (testing "warns regardless of case"
784 (tk-log-testutils/with-test-logging
785 (start-server ["sslv3"])
786 (is (logged? #"known vulnerabilities"))))
787 (testing "does not warn if sslv3 is not in the protocol list"
788 (tk-log-testutils/with-log-output logs
789 (start-server ["TLSv1"])
790 (is (= 0 (count (tk-log-testutils/logs-matching
791 #"known vulnerabilities" @logs))))))
792 (testing "does not warn with default settings"
793 (tk-log-testutils/with-log-output logs
794 (start-server nil)
795 (is (= 0 (count (tk-log-testutils/logs-matching
796 #"known vulnerabilities" @logs))))))))
797
798 (deftest sslv3-support-test
799 (testing "SSLv3 is not supported by default"
800 (with-app-with-config
801 app
802 [jetty9-service
803 hello-webservice]
804 jetty-ssl-pem-config
805 (is (thrown?
806 ConnectionClosedException
807 (http-get "https://localhost:8081/hi_world" (merge default-options-for-https-client
808 {:ssl-protocols ["SSLv3"]}))))))
809 (testing "SSLv3 is supported when configured"
810 (tk-log-testutils/with-test-logging
811 (with-app-with-config
812 app
813 [jetty9-service
814 hello-webservice]
815 (assoc-in jetty-ssl-pem-config [:webserver :ssl-protocols] ["SSLv3"])
816 (let [response (http-get "https://localhost:8081/hi_world" (merge default-options-for-https-client
817 {:ssl-protocols ["SSLv3"]}))]
818 (is (= (:status response) 200))
819 (is (= (:body response) "Hi World")))))))
0 (ns puppetlabs.trapperkeeper.services.webserver.normalized-uri-helpers-test
1 (:import (javax.servlet.http HttpServletRequest))
2 (:require [clojure.test :refer :all]
3 [puppetlabs.trapperkeeper.services.webserver.normalized-uri-helpers
4 :as normalized-uri-helpers]))
5
6 (defn normalize-uri-path-for-string
7 [uri]
8 (normalized-uri-helpers/normalize-uri-path
9 (reify HttpServletRequest
10 (getRequestURI [_] uri))))
11
12 (deftest normalize-uris-with-no-special-characters-tests
13 (testing "uris with no special characters are preserved after normalization"
14 (is (= "" (normalize-uri-path-for-string "")))
15 (is (= "/" (normalize-uri-path-for-string "/")))
16 (is (= "/foo" (normalize-uri-path-for-string "/foo")))
17 (is (= "/foo/bar" (normalize-uri-path-for-string "/foo/bar")))))
18
19 (deftest normalize-uris-with-plus-signs-in-path-segments-tests
20 (testing (str "non-percent encoded plus signs in uri path segments are "
21 "preserved after normalization")
22 (is (= "/foo+bar" (normalize-uri-path-for-string "/foo+bar")))
23 (is (= "/foo/bar+baz+bim"
24 (normalize-uri-path-for-string "/foo/bar+baz+bim"))))
25 (testing (str "percent encoded plus signs in uri path segments are "
26 "properly decoded after normalization")
27 (is (= "/foo+bar" (normalize-uri-path-for-string "/foo%2Bbar")))
28 (is (= "/foo/bar+baz+bim"
29 (normalize-uri-path-for-string "/foo/bar%2Bbaz%2Bb%69m")))))
30
31 (deftest normalize-uris-with-params-in-path-segments-tests
32 (testing (str "non-percent encoded parameters in uri path segments are "
33 "chopped off after normalization")
34 (is (= "/foo" (normalize-uri-path-for-string "/foo;foo=chump")))
35 (is (= "/foo/bar" (normalize-uri-path-for-string
36 "/foo/bar;bar=chocolate/baz;baz=bim"))))
37 (testing (str "percent-encoded parameters in uri path segments are properly "
38 "decoded after normalization")
39 (is (= "/foo;foo=chump" (normalize-uri-path-for-string
40 "/foo%3Bfoo=chump")))
41 (is (= "/foo/bar;bar=chocolate/baz;baz=bim"
42 (normalize-uri-path-for-string
43 "/foo/bar%3Bbar=chocolate/baz%3Bbaz=b%69m")))))
44
45 (deftest normalize-uris-with-percent-encoded-characters-tests
46 (testing (str "percent-encoded characters are properly decoded after "
47 "normalization")
48 (is (= "/foo" (normalize-uri-path-for-string "/%66oo")))
49 (is (= "/foo/b a r" (normalize-uri-path-for-string
50 "/foo%2f%62%20a%20%72"))))
51 (testing "malformed percent-encoded character throws exception"
52 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
53 "/foo/%b%a%r")))))
54
55 (deftest normalize-uris-with-relative-paths-tests
56 (testing "non percent-encoded relative paths are normalized as an error"
57 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string ".")))
58 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string "..")))
59 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string "/.")))
60 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
61 "/..")))
62 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
63 "/foo/.")))
64 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
65 "/foo/..")))
66 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
67 "/foo/./bar")))
68 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
69 "/foo/../bar"))))
70 (testing "percent-encoded relative paths are normalized as an error"
71 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
72 "%2E")))
73 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
74 "%2E%2E") ))
75 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
76 "/%2E") ))
77 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
78 "/%2E%2E") ))
79 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
80 "/foo/%2E") ))
81 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
82 "/foo/%2E%2E") ))
83 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
84 "/foo/%2E/bar") ))
85 (is (thrown? IllegalArgumentException (normalize-uri-path-for-string
86 "/foo/%2E%2E/bar") )))
87 (testing (str "period characters not representing relative paths are not "
88 "normalized as an error")
89 (is (= "/foo/.bar" (normalize-uri-path-for-string "/foo/.bar")))
90 (is (= "/foo/..bar" (normalize-uri-path-for-string "/foo/..bar")))
91 (is (= "/foo/bar." (normalize-uri-path-for-string "/foo/bar.")))
92 (is (= "/foo/bar.." (normalize-uri-path-for-string "/foo/bar..")))
93 (is (= "/foo/bar.../baz" (normalize-uri-path-for-string "/foo/bar.../baz")))
94 (is (= "/foo/.bar" (normalize-uri-path-for-string "/foo/%2Ebar")))
95 (is (= "/foo/..bar" (normalize-uri-path-for-string "/foo/%2E%2Ebar")))
96 (is (= "/foo/bar." (normalize-uri-path-for-string "/foo/bar%2E")))
97 (is (= "/foo/bar.." (normalize-uri-path-for-string "/foo/bar%2E%2E")))
98 (is (= "/foo/bar/.../baz" (normalize-uri-path-for-string
99 "/foo/bar/%2E%2E%2E/baz")))))
100
101 (deftest normalize-uri-with-overlong-utf8-chars-tests
102 (testing (str "utf-8 characters with overlong encodings are substituted "
103 "with replacement characters")
104 ;; %C0%AE is an overlong (leading zero-padded, two byte) encoding of the
105 ;; full stop (period) character. The full stop character's minimal
106 ;; encoding in UTF-8 is %2E. These tests validate that the normalization
107 ;; process substitutes replacement characters for the original characters
108 ;; in the string - as opposed to decoding the character back to the
109 ;; same character that the minimal form would decode to.
110 ;;
111 ;; From section 3 of RFC 3629 (https://tools.ietf.org/html/rfc3629):
112 ;;> Implementations of the decoding algorithm above MUST protect against
113 ;;> decoding invalid sequences. For instance, a naive implementation may
114 ;;> decode the overlong UTF-8 sequence C0 80 into the character U+0000,
115 ;;> or the surrogate pair ED A1 8C ED BE B4 into U+233B4. Decoding
116 ;;> invalid sequences may have security consequences or cause other
117 ;;> problems.
118 (is (= "��" (normalize-uri-path-for-string "%C0%AE")))
119 (is (= "/foo/��/��" (normalize-uri-path-for-string "/foo/%C0%AE/%C0%AE")))))
120
121 (deftest normalize-uris-with-redundant-slashes-tests
122 (testing "uris with redundant slashes are removed"
123 (is (= "/" (normalize-uri-path-for-string "//")))
124 (is (= "/foo" (normalize-uri-path-for-string "//foo")))
125 (is (= "/foo" (normalize-uri-path-for-string "///foo")))
126 (is (= "/foo/bar" (normalize-uri-path-for-string "/foo//bar")))
127 (is (= "/foo/bar/" (normalize-uri-path-for-string "/foo//bar///")))))
128
129 (deftest normalize-uris-with-percent-decoding-and-slash-removal
130 (testing (str "uris with both percent-encoded characters and redundant "
131 "slashes are properly normalized")
132 (is (= "/foo/b a r" (normalize-uri-path-for-string
133 "/%66oo%2f%2f%2f%62%20a r")))))
0 (ns puppetlabs.trapperkeeper.testutils.webrouting.common
1 (:require [puppetlabs.http.client.sync :as http-client]))
2
3 (defn http-get
4 ([url]
5 (http-get url {:as :text}))
6 ([url options]
7 (http-client/get url options)))
8
9 (def webrouting-plaintext-config
10 {:webserver {:port 8080}
11 :web-router-service
12 {:puppetlabs.trapperkeeper.services.webrouting.webrouting-service-handlers-test/test-dummy "/foo"
13 :puppetlabs.bar/bar-service "/bar"}})
14
15 (def default-options-for-https-client
16 {:ssl-cert "./dev-resources/config/jetty/ssl/certs/localhost.pem"
17 :ssl-key "./dev-resources/config/jetty/ssl/private_keys/localhost.pem"
18 :ssl-ca-cert "./dev-resources/config/jetty/ssl/certs/ca.pem"
19 :as :text})
0 (ns puppetlabs.trapperkeeper.testutils.webserver.common
1 (:require [puppetlabs.http.client.sync :as http-client]))
2
3 (defn http-get
4 ([url]
5 (http-get url {:as :text}))
6 ([url options]
7 (http-client/get url options)))
8
9 (def jetty-plaintext-config
10 {:webserver {:port 8080}})
11
12 (def jetty-plaintext-large-request-config
13 {:webserver {:port 8080
14 :request-header-max-size 16192}})
15
16 (def jetty-multiserver-plaintext-config
17 {:webserver {:foo {:port 8085}
18 :bar {:port 8080
19 :default-server true}}})
20
21 (def jetty-ssl-jks-config
22 {:webserver {:port 8080
23 :ssl-host "0.0.0.0"
24 :ssl-port 8081
25 :keystore "./dev-resources/config/jetty/ssl/keystore.jks"
26 :truststore "./dev-resources/config/jetty/ssl/truststore.jks"
27 :key-password "Kq8lG9LkISky9cDIYysiadxRx"
28 :trust-password "Kq8lG9LkISky9cDIYysiadxRx"}})
29
30 (def jetty-ssl-pem-config
31 {:webserver {:port 8080
32 :ssl-host "0.0.0.0"
33 :ssl-port 8081
34 :ssl-cert "./dev-resources/config/jetty/ssl/certs/localhost.pem"
35 :ssl-key "./dev-resources/config/jetty/ssl/private_keys/localhost.pem"
36 :ssl-ca-cert "./dev-resources/config/jetty/ssl/certs/ca.pem"}})
37
38 (def jetty-ssl-client-need-config
39 (assoc-in jetty-ssl-pem-config [:webserver :client-auth] "need"))
40
41 (def jetty-ssl-client-want-config
42 (assoc-in jetty-ssl-pem-config [:webserver :client-auth] "want"))
43
44 (def jetty-ssl-client-none-config
45 (assoc-in jetty-ssl-pem-config [:webserver :client-auth] "none"))
46
47 (def default-options-for-https-client
48 {:ssl-cert "./dev-resources/config/jetty/ssl/certs/localhost.pem"
49 :ssl-key "./dev-resources/config/jetty/ssl/private_keys/localhost.pem"
50 :ssl-ca-cert "./dev-resources/config/jetty/ssl/certs/ca.pem"
51 :as :text})
52
53 (def absurdly-large-cookie
54 (apply str (repeat 8192 "a")))
55
56 (def dev-resources-dir "./dev-resources/")
0 (ns puppetlabs.trapperkeeper.testutils.webserver
1 (:require [puppetlabs.trapperkeeper.services.webserver.jetty9-core :as jetty9]
2 [clojure.test :refer [is]]
3 [clojure.java.jmx :as jmx])
4 (:import (javax.management ObjectName)
5 (java.lang.management ManagementFactory)))
6
7 (defmacro with-test-webserver-and-config
8 "Constructs and starts an embedded Jetty on a random port with a custom
9 configuration and evaluates `body` inside a try/finally block that takes
10 care of tearing down the webserver.
11
12 `app` - The ring application the webserver should serve
13
14 `port-var` - Inside of `body`, the variable named `port-var`
15 contains the port number the webserver is listening on
16
17 `config` - Configuration to use for the webserver
18
19 Example:
20
21 (let [app (constantly {:status 200 :headers {} :body \"OK\"})]
22 (with-test-webserver app port {:request-header-max-size 1024}
23 ;; Hit the embedded webserver
24 (http-client/get (format \"http://localhost:%s\" port))))"
25 [app port-var config & body]
26 `(let [srv# (jetty9/start-webserver!
27 (jetty9/initialize-context)
28 (assoc ~config :port 0))
29 _# (jetty9/add-ring-handler srv# ~app "/" true false)
30 ~port-var (-> (:server srv#)
31 (.getConnectors)
32 (first)
33 (.getLocalPort))]
34 (try
35 ~@body
36 (finally
37 (jetty9/shutdown srv#)))))
38
39 (defmacro with-test-webserver
40 "Constructs and starts an embedded Jetty on a random port, and
41 evaluates `body` inside a try/finally block that takes care of
42 tearing down the webserver.
43
44 `app` - The ring application the webserver should serve
45
46 `port-var` - Inside of `body`, the variable named `port-var`
47 contains the port number the webserver is listening on
48
49 Example:
50
51 (let [app (constantly {:status 200 :headers {} :body \"OK\"})]
52 (with-test-webserver app port
53 ;; Hit the embedded webserver
54 (http-client/get (format \"http://localhost:%s\" port))))"
55 [app port-var & body]
56 `(with-test-webserver-and-config
57 ~app
58 ~port-var
59 {}
60 ~@body))
61
62 (defn get-jetty-mbean-object-names
63 "Queries the JVM MBean Registry and returns the ObjectNames of all of the
64 Jetty MBean container objects. For the purposes of tk-j9, there should be
65 one ObjectName per Jetty Server instance, *if* the jmx-enabled setting is set
66 to 'true'."
67 []
68 (jmx/mbean-names "org.eclipse.jetty.jmx:type=mbeancontainer,*"))
69
70 (defn assert-clean-shutdown
71 "A test fixture that can be used to ensure that all of the Jetty instances have
72 been cleaned up properly by the tests in a particular test namespace."
73 [f]
74 (f)
75 ;; This sucks, because if this assertion fails, it will not give any clue as to
76 ;; which test caused it to fail beyond just the test namespace. However, I tried
77 ;; several things (such as throwing an exception here rather than having an assertion),
78 ;; and nothing gave any better debugging info because the metadata about which
79 ;; test we're running and the call stack for the test function itself are simply
80 ;; not available from inside a fixture. So I decided that even those these aren't
81 ;; super fun to debug, it was better than any other option in terms of enforcing
82 ;; that the tests clean up after themselves properly.
83 (is (= 0 (count (get-jetty-mbean-object-names)))))
0 package appender;
1
2 import ch.qos.logback.access.PatternLayout;
3 import ch.qos.logback.access.PatternLayoutEncoder;
4 import ch.qos.logback.access.spi.IAccessEvent;
5 import ch.qos.logback.core.AppenderBase;
6 import ch.qos.logback.core.Layout;
7 import ch.qos.logback.core.encoder.Encoder;
8
9 import java.util.ArrayList;
10 import java.util.List;
11
12 /**
13 * Created by Preben Ingvaldsen on 9/17/14.
14 */
15 public class TestListAppender<E> extends AppenderBase<E> {
16 public static List list = new ArrayList();
17 protected PatternLayoutEncoder encoder;
18 protected void append(E e) {
19 PatternLayout layout = (PatternLayout)encoder.getLayout();
20 String s = layout.doLayout((IAccessEvent)e);
21 list.add(s);
22 }
23
24 public void setEncoder(Encoder<E> encoder) {
25 this.encoder = (PatternLayoutEncoder)encoder;
26 }
27 }
0 package servlet;
1
2 import java.io.IOException;
3 import javax.servlet.ServletConfig;
4 import javax.servlet.ServletException;
5 import javax.servlet.http.HttpServlet;
6 import javax.servlet.http.HttpServletRequest;
7 import javax.servlet.http.HttpServletResponse;
8
9 public class SimpleServlet extends HttpServlet {
10 private String body;
11 private ServletConfig config;
12
13 public SimpleServlet(String body) {
14 this.body = body;
15 this.config = null;
16 }
17
18 @Override
19 public void init(final ServletConfig config) throws ServletException {
20 this.config = config;
21 }
22
23 @Override
24 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
25 response.setContentType("text/html");
26 response.setStatus(HttpServletResponse.SC_OK);
27 String pathInfo = request.getPathInfo();
28 if (pathInfo != null && pathInfo.length() > 1 && pathInfo.charAt(0) == '/' && config != null)
29 {
30 response.getWriter().print(config.getInitParameter(pathInfo.substring(1)));
31 }
32 else
33 {
34 response.getWriter().print(body);
35 }
36 }
37 }
38