/http-server/src/test/java/io/airlift/http/server/TestHttpServerModule.java
Java | 379 lines | 313 code | 47 blank | 19 comment | 3 complexity | 803180c58aa809426ab471c0d8fce7c2 MD5 | raw file
1/*
2 * Copyright 2010 Proofpoint, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package io.airlift.http.server;
17
18import com.google.common.base.Charsets;
19import com.google.common.base.Joiner;
20import com.google.common.collect.ArrayListMultimap;
21import com.google.common.collect.ImmutableMap;
22import com.google.common.collect.ListMultimap;
23import com.google.common.io.ByteStreams;
24import com.google.common.io.Files;
25import com.google.common.net.HttpHeaders;
26import com.google.common.net.MediaType;
27import com.google.inject.Binder;
28import com.google.inject.Guice;
29import com.google.inject.Injector;
30import com.google.inject.Key;
31import com.google.inject.Module;
32import com.google.inject.Scopes;
33import com.google.inject.multibindings.Multibinder;
34import io.airlift.configuration.ConfigurationFactory;
35import io.airlift.configuration.ConfigurationModule;
36import io.airlift.event.client.EventModule;
37import io.airlift.event.client.InMemoryEventClient;
38import io.airlift.event.client.InMemoryEventModule;
39import io.airlift.http.client.HttpClient;
40import io.airlift.http.client.HttpStatus;
41import io.airlift.http.client.HttpUriBuilder;
42import io.airlift.http.client.StatusResponseHandler.StatusResponse;
43import io.airlift.http.client.StringResponseHandler.StringResponse;
44import io.airlift.http.client.jetty.JettyHttpClient;
45import io.airlift.log.Logging;
46import io.airlift.node.NodeInfo;
47import io.airlift.node.NodeModule;
48import io.airlift.testing.FileUtils;
49import io.airlift.tracetoken.TraceTokenModule;
50import org.testng.Assert;
51import org.testng.annotations.AfterMethod;
52import org.testng.annotations.BeforeMethod;
53import org.testng.annotations.BeforeSuite;
54import org.testng.annotations.Test;
55
56import javax.servlet.Filter;
57import javax.servlet.Servlet;
58import javax.servlet.ServletException;
59import javax.servlet.http.HttpServlet;
60import javax.servlet.http.HttpServletRequest;
61import javax.servlet.http.HttpServletResponse;
62
63import java.io.File;
64import java.io.IOException;
65import java.net.URI;
66import java.util.List;
67import java.util.Map;
68import java.util.Map.Entry;
69
70import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
71import static com.google.common.net.HttpHeaders.REFERER;
72import static com.google.common.net.HttpHeaders.USER_AGENT;
73import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
74import static io.airlift.http.client.HttpUriBuilder.uriBuilderFrom;
75import static io.airlift.http.client.Request.Builder.prepareGet;
76import static io.airlift.http.client.Request.Builder.preparePost;
77import static io.airlift.http.client.StaticBodyGenerator.createStaticBodyGenerator;
78import static io.airlift.http.client.StatusResponseHandler.createStatusResponseHandler;
79import static io.airlift.http.client.StringResponseHandler.createStringResponseHandler;
80import static io.airlift.http.server.HttpServerBinder.httpServerBinder;
81import static java.util.Collections.nCopies;
82import static org.testng.Assert.assertEquals;
83import static org.testng.Assert.assertNotNull;
84import static org.testng.Assert.assertNull;
85import static org.testng.Assert.assertTrue;
86
87public class TestHttpServerModule
88{
89 private File tempDir;
90
91 @BeforeSuite
92 public void setupSuite()
93 {
94 Logging.initialize();
95 }
96
97 @BeforeMethod
98 public void setup()
99 throws IOException
100 {
101 tempDir = Files.createTempDir().getCanonicalFile(); // getCanonicalFile needed to get around Issue 365 (http://code.google.com/p/guava-libraries/issues/detail?id=365)
102 }
103
104 @AfterMethod
105 public void tearDown()
106 throws IOException
107 {
108 FileUtils.deleteRecursively(tempDir);
109 }
110
111 @Test
112 public void testCanConstructServer()
113 throws Exception
114 {
115 Map<String, String> properties = new ImmutableMap.Builder<String, String>()
116 .put("node.environment", "test")
117 .put("http-server.http.port", "0")
118 .put("http-server.log.path", new File(tempDir, "http-request.log").getAbsolutePath())
119 .build();
120
121 ConfigurationFactory configFactory = new ConfigurationFactory(properties);
122 Injector injector = Guice.createInjector(new HttpServerModule(),
123 new NodeModule(),
124 new ConfigurationModule(configFactory),
125 new EventModule(),
126 new Module()
127 {
128 @Override
129 public void configure(Binder binder)
130 {
131 binder.bind(Servlet.class).annotatedWith(TheServlet.class).to(DummyServlet.class);
132 }
133 });
134
135 HttpServer server = injector.getInstance(HttpServer.class);
136 assertNotNull(server);
137 }
138
139 @Test
140 public void testHttpServerUri()
141 throws Exception
142 {
143 Map<String, String> properties = new ImmutableMap.Builder<String, String>()
144 .put("node.environment", "test")
145 .put("http-server.http.port", "0")
146 .put("http-server.log.path", new File(tempDir, "http-request.log").getAbsolutePath())
147 .build();
148
149 ConfigurationFactory configFactory = new ConfigurationFactory(properties);
150 Injector injector = Guice.createInjector(new HttpServerModule(),
151 new NodeModule(),
152 new ConfigurationModule(configFactory),
153 new EventModule(),
154 new Module()
155 {
156 @Override
157 public void configure(Binder binder)
158 {
159 binder.bind(Servlet.class).annotatedWith(TheServlet.class).to(DummyServlet.class);
160 }
161 });
162
163 NodeInfo nodeInfo = injector.getInstance(NodeInfo.class);
164 HttpServer server = injector.getInstance(HttpServer.class);
165 assertNotNull(server);
166 server.start();
167 try {
168 HttpServerInfo httpServerInfo = injector.getInstance(HttpServerInfo.class);
169 assertNotNull(httpServerInfo);
170 assertNotNull(httpServerInfo.getHttpUri());
171 assertEquals(httpServerInfo.getHttpUri().getScheme(), "http");
172 assertEquals(httpServerInfo.getHttpUri().getHost(), nodeInfo.getInternalIp().getHostAddress());
173 assertNull(httpServerInfo.getHttpsUri());
174 }
175 catch (Exception e) {
176 server.stop();
177 }
178 }
179
180 @Test
181 public void testServer()
182 throws Exception
183 {
184 Map<String, String> properties = new ImmutableMap.Builder<String, String>()
185 .put("node.environment", "test")
186 .put("http-server.http.port", "0")
187 .put("http-server.log.path", new File(tempDir, "http-request.log").getAbsolutePath())
188 .build();
189
190 ConfigurationFactory configFactory = new ConfigurationFactory(properties);
191 Injector injector = Guice.createInjector(new HttpServerModule(),
192 new NodeModule(),
193 new ConfigurationModule(configFactory),
194 new EventModule(),
195 new Module()
196 {
197 @Override
198 public void configure(Binder binder)
199 {
200 binder.bind(Servlet.class).annotatedWith(TheServlet.class).to(DummyServlet.class);
201 Multibinder.newSetBinder(binder, Filter.class, TheServlet.class).addBinding().to(DummyFilter.class).in(Scopes.SINGLETON);
202 httpServerBinder(binder).bindResource("/", "webapp/user").withWelcomeFile("user-welcome.txt");
203 httpServerBinder(binder).bindResource("/", "webapp/user2");
204 httpServerBinder(binder).bindResource("path", "webapp/user").withWelcomeFile("user-welcome.txt");
205 httpServerBinder(binder).bindResource("path", "webapp/user2");
206 }
207 });
208
209 HttpServerInfo httpServerInfo = injector.getInstance(HttpServerInfo.class);
210
211 HttpServer server = injector.getInstance(HttpServer.class);
212 server.start();
213
214 try (HttpClient client = new JettyHttpClient()) {
215
216 // test servlet bound correctly
217 URI httpUri = httpServerInfo.getHttpUri();
218 StatusResponse response = client.execute(prepareGet().setUri(httpUri).build(), createStatusResponseHandler());
219
220 assertEquals(response.getStatusCode(), HttpServletResponse.SC_OK);
221
222 // test filter bound correctly
223 response = client.execute(prepareGet().setUri(httpUri.resolve("/filter")).build(), createStatusResponseHandler());
224
225 assertEquals(response.getStatusCode(), HttpServletResponse.SC_PAYMENT_REQUIRED);
226 assertEquals(response.getStatusMessage(), "filtered");
227
228 // test http resources
229 assertResource(httpUri, client, "", "welcome user!");
230 assertResource(httpUri, client, "user-welcome.txt", "welcome user!");
231 assertResource(httpUri, client, "user.txt", "user");
232 assertResource(httpUri, client, "user2.txt", "user2");
233 assertResource(httpUri, client, "path", "welcome user!");
234 assertResource(httpUri, client, "path/", "welcome user!");
235 assertResource(httpUri, client, "path/user-welcome.txt", "welcome user!");
236 assertResource(httpUri, client, "path/user.txt", "user");
237 assertResource(httpUri, client, "path/user2.txt", "user2");
238 }
239 finally {
240 server.stop();
241 }
242 }
243
244 private void assertResource(URI baseUri, HttpClient client, String path, String contents)
245 {
246 HttpUriBuilder uriBuilder = uriBuilderFrom(baseUri);
247 StringResponse data = client.execute(prepareGet().setUri(uriBuilder.appendPath(path).build()).build(), createStringResponseHandler());
248 assertEquals(data.getStatusCode(), HttpStatus.OK.code());
249 MediaType contentType = MediaType.parse(data.getHeader(HttpHeaders.CONTENT_TYPE));
250 assertTrue(PLAIN_TEXT_UTF_8.is(contentType), "Expected text/plain but got " + contentType);
251 assertEquals(data.getBody().trim(), contents);
252 }
253
254 @Test
255 public void testHttpRequestEvent()
256 throws Exception
257 {
258 Map<String, String> properties = new ImmutableMap.Builder<String, String>()
259 .put("node.environment", "test")
260 .put("http-server.http.port", "0")
261 .put("http-server.log.path", new File(tempDir, "http-request.log").getAbsolutePath())
262 .build();
263
264 ConfigurationFactory configFactory = new ConfigurationFactory(properties);
265 Injector injector = Guice.createInjector(new HttpServerModule(),
266 new NodeModule(),
267 new ConfigurationModule(configFactory),
268 new InMemoryEventModule(),
269 new TraceTokenModule(),
270 new Module()
271 {
272 @Override
273 public void configure(Binder binder)
274 {
275 binder.bind(Servlet.class).annotatedWith(TheServlet.class).to(EchoServlet.class).in(Scopes.SINGLETON);
276 }
277 });
278
279 HttpServerInfo httpServerInfo = injector.getInstance(HttpServerInfo.class);
280 InMemoryEventClient eventClient = injector.getInstance(InMemoryEventClient.class);
281 EchoServlet echoServlet = (EchoServlet) injector.getInstance(Key.get(Servlet.class, TheServlet.class));
282
283 HttpServer server = injector.getInstance(HttpServer.class);
284 server.start();
285
286 URI requestUri = httpServerInfo.getHttpUri().resolve("/my/path");
287 String userAgent = "my-user-agent";
288 String referrer = "http://www.google.com";
289 String token = "this is a trace token";
290 String requestBody = Joiner.on(" ").join(nCopies(50, "request"));
291 String requestContentType = "request/type";
292
293 int responseCode = 555;
294 String responseBody = Joiner.on(" ").join(nCopies(100, "response"));
295 String responseContentType = "response/type";
296
297 echoServlet.responseBody = responseBody;
298 echoServlet.responseStatusCode = responseCode;
299 echoServlet.responseHeaders.put("Content-Type", responseContentType);
300
301 long beforeRequest = System.currentTimeMillis();
302 long afterRequest;
303 try (JettyHttpClient client = new JettyHttpClient()) {
304
305 // test servlet bound correctly
306 StringResponse response = client.execute(
307 preparePost().setUri(requestUri)
308 .addHeader(USER_AGENT, userAgent)
309 .addHeader(CONTENT_TYPE, requestContentType)
310 .addHeader(REFERER, referrer)
311 .addHeader("X-Airlift-TraceToken", token)
312 .setBodyGenerator(createStaticBodyGenerator(requestBody, Charsets.UTF_8))
313 .build(),
314 createStringResponseHandler());
315
316 afterRequest = System.currentTimeMillis();
317
318 assertEquals(response.getStatusCode(), responseCode);
319 assertEquals(response.getBody(), responseBody);
320 assertEquals(response.getHeader("Content-Type"), responseContentType);
321 }
322 finally {
323 server.stop();
324 }
325
326 List<Object> events = eventClient.getEvents();
327 Assert.assertEquals(events.size(), 1);
328 HttpRequestEvent event = (HttpRequestEvent) events.get(0);
329
330
331 Assert.assertEquals(event.getClientAddress(), echoServlet.remoteAddress);
332 Assert.assertEquals(event.getProtocol(), "http");
333 Assert.assertEquals(event.getMethod(), "POST");
334 Assert.assertEquals(event.getRequestUri(), requestUri.getPath());
335 Assert.assertNull(event.getUser());
336 Assert.assertEquals(event.getAgent(), userAgent);
337 Assert.assertEquals(event.getReferrer(), referrer);
338 Assert.assertEquals(event.getTraceToken(), token);
339
340 Assert.assertEquals(event.getRequestSize(), requestBody.length());
341 Assert.assertEquals(event.getRequestContentType(), requestContentType);
342
343 Assert.assertEquals(event.getResponseSize(), responseBody.length());
344 Assert.assertEquals(event.getResponseCode(), responseCode);
345 Assert.assertEquals(event.getResponseContentType(), responseContentType);
346
347 Assert.assertTrue(event.getTimeStamp().getMillis() >= beforeRequest);
348 Assert.assertTrue(event.getTimeToLastByte() <= afterRequest - beforeRequest);
349 Assert.assertNotNull(event.getTimeToFirstByte());
350 Assert.assertTrue(event.getTimeToDispatch() <= event.getTimeToFirstByte());
351 Assert.assertTrue(event.getTimeToFirstByte() <= event.getTimeToLastByte());
352 }
353
354 private static final class EchoServlet extends HttpServlet
355 {
356 private int responseStatusCode = 300;
357 private final ListMultimap<String, String> responseHeaders = ArrayListMultimap.create();
358 public String responseBody;
359 private String remoteAddress;
360
361 @Override
362 protected void service(HttpServletRequest request, HttpServletResponse response)
363 throws ServletException, IOException
364 {
365 ByteStreams.copy(request.getInputStream(), ByteStreams.nullOutputStream());
366
367 remoteAddress = request.getRemoteAddr();
368 for (Entry<String, String> entry : responseHeaders.entries()) {
369 response.addHeader(entry.getKey(), entry.getValue());
370 }
371
372 response.setStatus(responseStatusCode);
373
374 if (responseBody != null) {
375 response.getOutputStream().write(responseBody.getBytes(Charsets.UTF_8));
376 }
377 }
378 }
379}