New upstream version 1.7.0
Apollon Oikonomopoulos
6 years ago
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 | [![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----- |
Binary diff not shown
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----- |
Binary diff not shown
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----- |
Binary diff not shown
Binary diff not shown
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 | } |
+28
-0
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))) |
+124
-0
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)) |
+215
-0
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 |
+94
-0
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.")))))⏎ |
+99
-0
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)))))))) |
+196
-0
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))))))))) |
+536
-0
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")))))))) |
+225
-0
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.")))))⏎ |
+801
-0
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"))))))) |
+134
-0
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 |