1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.core5.benchmark;
28
29 import java.io.File;
30 import java.io.IOException;
31 import java.net.SocketAddress;
32 import java.net.URI;
33 import java.nio.ByteBuffer;
34 import java.nio.channels.ByteChannel;
35 import java.nio.file.Paths;
36 import java.util.List;
37 import java.util.concurrent.CountDownLatch;
38 import java.util.concurrent.TimeUnit;
39 import java.util.concurrent.atomic.AtomicLong;
40 import java.util.concurrent.locks.Lock;
41
42 import javax.net.ssl.SSLContext;
43
44 import org.apache.commons.cli.CommandLine;
45 import org.apache.commons.cli.CommandLineParser;
46 import org.apache.commons.cli.DefaultParser;
47 import org.apache.commons.cli.Options;
48 import org.apache.hc.core5.http.Header;
49 import org.apache.hc.core5.http.HttpConnection;
50 import org.apache.hc.core5.http.HttpHost;
51 import org.apache.hc.core5.http.HttpRequest;
52 import org.apache.hc.core5.http.HttpResponse;
53 import org.apache.hc.core5.http.HttpVersion;
54 import org.apache.hc.core5.http.impl.Http1StreamListener;
55 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
56 import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy;
57 import org.apache.hc.core5.http.protocol.HttpCoreContext;
58 import org.apache.hc.core5.http.protocol.HttpProcessorBuilder;
59 import org.apache.hc.core5.http.protocol.RequestExpectContinue;
60 import org.apache.hc.core5.http.protocol.RequestUserAgent;
61 import org.apache.hc.core5.http2.HttpVersionPolicy;
62 import org.apache.hc.core5.http2.config.H2Config;
63 import org.apache.hc.core5.http2.frame.FramePrinter;
64 import org.apache.hc.core5.http2.frame.FrameType;
65 import org.apache.hc.core5.http2.frame.RawFrame;
66 import org.apache.hc.core5.http2.impl.nio.H2StreamListener;
67 import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
68 import org.apache.hc.core5.http2.protocol.H2RequestConnControl;
69 import org.apache.hc.core5.http2.protocol.H2RequestContent;
70 import org.apache.hc.core5.http2.protocol.H2RequestTargetHost;
71 import org.apache.hc.core5.io.CloseMode;
72 import org.apache.hc.core5.reactor.Command;
73 import org.apache.hc.core5.reactor.IOEventHandler;
74 import org.apache.hc.core5.reactor.IOReactorConfig;
75 import org.apache.hc.core5.reactor.IOSession;
76 import org.apache.hc.core5.ssl.SSLContextBuilder;
77 import org.apache.hc.core5.ssl.SSLContexts;
78 import org.apache.hc.core5.util.Timeout;
79
80
81
82
83
84
85 public class HttpBenchmark {
86
87 private final BenchmarkConfig config;
88
89 public static void main(final String[] args) throws Exception {
90
91 final Options options = CommandLineUtils.getOptions();
92 final CommandLineParser parser = new DefaultParser();
93 final CommandLine cmd = parser.parse(options, args);
94
95 if (args.length == 0 || cmd.hasOption('h') || cmd.getArgs().length != 1) {
96 CommandLineUtils.showUsage(options);
97 System.exit(1);
98 }
99
100 final BenchmarkConfig config = CommandLineUtils.parseCommandLine(cmd);
101
102 if (config.getUri() == null) {
103 CommandLineUtils.showUsage(options);
104 System.exit(1);
105 }
106
107 final HttpBenchmark httpBenchmark = new HttpBenchmark(config);
108 final Results results = httpBenchmark.execute();
109 System.out.println();
110 ResultFormatter.print(System.out, results);
111 }
112
113 public HttpBenchmark(final BenchmarkConfig config) {
114 super();
115 this.config = config != null ? config : BenchmarkConfig.custom().build();
116 }
117
118 public Results execute() throws Exception {
119 final HttpProcessorBuilder builder = HttpProcessorBuilder.create()
120 .addAll(
121 H2RequestContent.INSTANCE,
122 H2RequestTargetHost.INSTANCE,
123 H2RequestConnControl.INSTANCE,
124 new RequestUserAgent("HttpCore-AB/5.0"));
125 if (this.config.isUseExpectContinue()) {
126 builder.add(RequestExpectContinue.INSTANCE);
127 }
128
129 final SSLContext sslContext;
130 if ("https".equals(config.getUri().getScheme())) {
131 final SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
132 sslContextBuilder.setProtocol("SSL");
133 if (config.isDisableSSLVerification()) {
134 sslContextBuilder.loadTrustMaterial(null, (chain, authType) -> true);
135 } else if (config.getTrustStorePath() != null) {
136 sslContextBuilder.loadTrustMaterial(
137 new File(config.getTrustStorePath()),
138 config.getTrustStorePassword() != null ? config.getTrustStorePassword().toCharArray() : null);
139 }
140 if (config.getIdentityStorePath() != null) {
141 sslContextBuilder.loadKeyMaterial(
142 Paths.get(config.getIdentityStorePath()),
143 config.getIdentityStorePassword() != null ? config.getIdentityStorePassword().toCharArray() : null,
144 config.getIdentityStorePassword() != null ? config.getIdentityStorePassword().toCharArray() : null);
145 }
146 sslContext = sslContextBuilder.build();
147 } else {
148 sslContext = SSLContexts.createSystemDefault();
149 }
150
151 final HttpVersionPolicy versionPolicy;
152 if (config.isForceHttp2()) {
153 versionPolicy = HttpVersionPolicy.FORCE_HTTP_2;
154 } else {
155 if (sslContext != null) {
156 versionPolicy = HttpVersionPolicy.NEGOTIATE;
157 } else {
158 versionPolicy = HttpVersionPolicy.FORCE_HTTP_1;
159 }
160 }
161
162 final Stats stats = new Stats();
163 try (final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap()
164 .setHttpProcessor(builder.build())
165 .setTlsStrategy(new BasicClientTlsStrategy(sslContext))
166 .setVersionPolicy(versionPolicy)
167 .setH2Config(H2Config.custom()
168 .setPushEnabled(false)
169 .build())
170 .setIOSessionDecorator(ioSession -> new IOSession() {
171
172 @Override
173 public String getId() {
174 return ioSession.getId();
175 }
176
177 @Override
178 public Lock getLock() {
179 return ioSession.getLock();
180 }
181
182 @Override
183 public void enqueue(final Command command, final Command.Priority priority) {
184 ioSession.enqueue(command, priority);
185 }
186
187 @Override
188 public boolean hasCommands() {
189 return ioSession.hasCommands();
190 }
191
192 @Override
193 public Command poll() {
194 return ioSession.poll();
195 }
196
197 @Override
198 public ByteChannel channel() {
199 return ioSession.channel();
200 }
201
202 @Override
203 public SocketAddress getRemoteAddress() {
204 return ioSession.getRemoteAddress();
205 }
206
207 @Override
208 public SocketAddress getLocalAddress() {
209 return ioSession.getLocalAddress();
210 }
211
212 @Override
213 public int getEventMask() {
214 return ioSession.getEventMask();
215 }
216
217 @Override
218 public void setEventMask(final int ops) {
219 ioSession.setEventMask(ops);
220 }
221
222 @Override
223 public void setEvent(final int op) {
224 ioSession.setEvent(op);
225 }
226
227 @Override
228 public void clearEvent(final int op) {
229 ioSession.clearEvent(op);
230 }
231
232 @Override
233 public void close() {
234 ioSession.close();
235 }
236
237 @Override
238 public Status getStatus() {
239 return ioSession.getStatus();
240 }
241
242 @Override
243 public int read(final ByteBuffer dst) throws IOException {
244 final int bytesRead = ioSession.read(dst);
245 if (bytesRead > 0) {
246 stats.incTotalBytesRecv(bytesRead);
247 }
248 return bytesRead;
249 }
250
251 @Override
252 public int write(final ByteBuffer src) throws IOException {
253 final int bytesWritten = ioSession.write(src);
254 if (bytesWritten > 0) {
255 stats.incTotalBytesSent(bytesWritten);
256 }
257 return bytesWritten;
258 }
259
260 @Override
261 public boolean isOpen() {
262 return ioSession.isOpen();
263 }
264
265 @Override
266 public Timeout getSocketTimeout() {
267 return ioSession.getSocketTimeout();
268 }
269
270 @Override
271 public void setSocketTimeout(final Timeout timeout) {
272 ioSession.setSocketTimeout(timeout);
273 }
274
275 @Override
276 public long getLastReadTime() {
277 return ioSession.getLastReadTime();
278 }
279
280 @Override
281 public long getLastWriteTime() {
282 return ioSession.getLastWriteTime();
283 }
284
285 @Override
286 public long getLastEventTime() {
287 return ioSession.getLastEventTime();
288 }
289
290 @Override
291 public void updateReadTime() {
292 ioSession.updateReadTime();
293 }
294
295 @Override
296 public void updateWriteTime() {
297 ioSession.updateWriteTime();
298 }
299
300 @Override
301 public void close(final CloseMode closeMode) {
302 ioSession.close(closeMode);
303 }
304
305 @Override
306 public IOEventHandler getHandler() {
307 return ioSession.getHandler();
308 }
309
310 @Override
311 public void upgrade(final IOEventHandler handler) {
312 ioSession.upgrade(handler);
313 }
314
315 })
316 .setStreamListener(new Http1StreamListener() {
317
318 @Override
319 public void onRequestHead(final HttpConnection connection, final HttpRequest request) {
320 if (config.getVerbosity() >= 3) {
321 System.out.println(">> " + request.getMethod() + " " + request.getRequestUri());
322 final Header[] headers = request.getHeaders();
323 for (final Header header : headers) {
324 System.out.println(">> " + header);
325 }
326 System.out.println();
327 }
328 }
329
330 @Override
331 public void onResponseHead(final HttpConnection connection, final HttpResponse response) {
332 if (config.getVerbosity() >= 3) {
333 System.out.println("<< " + response.getCode() + " " + response.getReasonPhrase());
334 final Header[] headers = response.getHeaders();
335 for (final Header header : headers) {
336 System.out.println("<< " + header);
337 }
338 System.out.println();
339 }
340 }
341
342 @Override
343 public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) {
344 }
345
346 })
347 .setStreamListener(new H2StreamListener() {
348
349 private final FramePrinter framePrinter = new FramePrinter();
350
351 @Override
352 public void onHeaderInput(
353 final HttpConnection connection,
354 final int streamId,
355 final List<? extends Header> headers) {
356 if (config.getVerbosity() >= 3) {
357 for (final Header header : headers) {
358 System.out.println("<< " + header);
359 }
360 System.out.println();
361 }
362 }
363
364 @Override
365 public void onHeaderOutput(
366 final HttpConnection connection,
367 final int streamId,
368 final List<? extends Header> headers) {
369 if (config.getVerbosity() >= 3) {
370 for (final Header header : headers) {
371 System.out.println(">> " + header);
372 }
373 System.out.println();
374 }
375 }
376
377 @Override
378 public void onFrameInput(
379 final HttpConnection connection,
380 final int streamId,
381 final RawFrame frame) {
382 if (config.getVerbosity() >= 4) {
383 System.out.print("<< ");
384 try {
385 framePrinter.printFrameInfo(frame, System.out);
386 System.out.println();
387 if (!frame.isType(FrameType.DATA)) {
388 framePrinter.printPayload(frame, System.out);
389 System.out.println();
390 }
391 } catch (final IOException ignore) {
392 }
393 }
394 }
395
396 @Override
397 public void onFrameOutput(
398 final HttpConnection connection,
399 final int streamId,
400 final RawFrame frame) {
401 if (config.getVerbosity() >= 4) {
402 System.out.print(">> ");
403 try {
404 framePrinter.printFrameInfo(frame, System.out);
405 System.out.println();
406 if (!frame.isType(FrameType.DATA)) {
407 framePrinter.printPayload(frame, System.out);
408 System.out.println();
409 }
410 } catch (final IOException ignore) {
411 }
412 }
413 }
414
415 @Override
416 public void onInputFlowControl(
417 final HttpConnection connection,
418 final int streamId,
419 final int delta,
420 final int actualSize) {
421 if (config.getVerbosity() >= 5) {
422 System.out.println("<< stream " + streamId + ": " + actualSize + " " + delta);
423 }
424 }
425
426 @Override
427 public void onOutputFlowControl(
428 final HttpConnection connection,
429 final int streamId,
430 final int delta,
431 final int actualSize) {
432 if (config.getVerbosity() >= 5) {
433 System.out.println(">> stream " + streamId + ": " + actualSize + " " + delta);
434 }
435 }
436
437 })
438 .setIOReactorConfig(IOReactorConfig.custom()
439 .setSoTimeout(config.getSocketTimeout())
440 .build())
441 .create()) {
442 requester.setDefaultMaxPerRoute(config.getConcurrencyLevel());
443 requester.setMaxTotal(config.getConcurrencyLevel() * 2);
444 requester.start();
445 return doExecute(requester, stats);
446 }
447 }
448
449 private Results doExecute(final HttpAsyncRequester requester, final Stats stats) throws Exception {
450
451 final URI requestUri = config.getUri();
452 final HttpHost host = new HttpHost(requestUri.getScheme(), requestUri.getHost(), requestUri.getPort());
453
454 final AtomicLong requestCount = new AtomicLong(config.getRequests());
455
456 final HttpVersion version = HttpVersion.HTTP_1_1;
457
458 final CountDownLatch completionLatch = new CountDownLatch(config.getConcurrencyLevel());
459 final BenchmarkWorker[] workers = new BenchmarkWorker[config.getConcurrencyLevel()];
460 for (int i = 0; i < workers.length; i++) {
461 final HttpCoreContext context = HttpCoreContext.create();
462 context.setProtocolVersion(version);
463 final BenchmarkWorker worker = new BenchmarkWorker(
464 requester,
465 host,
466 context,
467 requestCount,
468 completionLatch,
469 stats,
470 config);
471 workers[i] = worker;
472 }
473
474 final long deadline = config.getTimeLimit() != null ? config.getTimeLimit().toMilliseconds() : Long.MAX_VALUE;
475
476 final long startTime = System.currentTimeMillis();
477
478 for (int i = 0; i < workers.length; i++) {
479 workers[i].execute();
480 }
481
482 completionLatch.await(deadline, TimeUnit.MILLISECONDS);
483
484 if (config.getVerbosity() >= 3) {
485 System.out.println("...done");
486 }
487
488 final long endTime = System.currentTimeMillis();
489
490 for (int i = 0; i < workers.length; i++) {
491 workers[i].releaseResources();
492 }
493
494 return new Results(
495 stats.getServerName(),
496 stats.getVersion(),
497 host.getHostName(),
498 host.getPort() > 0 ? host.getPort() : host.getSchemeName().equalsIgnoreCase("https") ? 443 : 80,
499 requestUri.toASCIIString(),
500 stats.getContentLength(),
501 config.getConcurrencyLevel(),
502 endTime - startTime,
503 stats.getSuccessCount(),
504 stats.getFailureCount(),
505 stats.getKeepAliveCount(),
506 stats.getTotalBytesRecv(),
507 stats.getTotalBytesSent(),
508 stats.getTotalContentLength());
509 }
510
511 }