Merge pull request #433 from patriot1burke/master
netty4 fixes
Bill Burke
10 years ago
16 | 16 | <groupId>org.jboss.resteasy</groupId> |
17 | 17 | <artifactId>resteasy-jaxrs</artifactId> |
18 | 18 | <version>${project.version}</version> |
19 | </dependency> | |
20 | <dependency> | |
21 | <groupId>org.jboss.resteasy</groupId> | |
22 | <artifactId>resteasy-client</artifactId> | |
23 | <version>${project.version}</version> | |
24 | <scope>test</scope> | |
25 | </dependency> | |
26 | <dependency> | |
27 | <groupId>org.jboss.resteasy</groupId> | |
28 | <artifactId>resteasy-jaxb-provider</artifactId> | |
29 | <version>${project.version}</version> | |
30 | <scope>test</scope> | |
19 | 31 | </dependency> |
20 | 32 | <dependency> |
21 | 33 | <groupId>io.netty</groupId> |
+42
-9
53 | 53 | private final boolean is100ContinueExpected; |
54 | 54 | private NettyExecutionContext executionContext; |
55 | 55 | private final ChannelHandlerContext ctx; |
56 | private volatile boolean flushed; | |
56 | 57 | |
57 | 58 | public NettyHttpRequest(ChannelHandlerContext ctx, ResteasyHttpHeaders httpHeaders, ResteasyUriInfo uri, String httpMethod, SynchronousDispatcher dispatcher, NettyHttpResponse response, boolean is100ContinueExpected) |
58 | 59 | { |
103 | 104 | public ResteasyAsynchronousContext getAsyncContext() |
104 | 105 | { |
105 | 106 | return executionContext; |
107 | } | |
108 | ||
109 | public boolean isFlushed() | |
110 | { | |
111 | return flushed; | |
106 | 112 | } |
107 | 113 | |
108 | 114 | @Override |
331 | 337 | finally |
332 | 338 | { |
333 | 339 | done = true; |
334 | ctx.writeAndFlush(nettyResponse.getDefaultFullHttpResponse()); | |
335 | ctx.close(); | |
340 | nettyFlush(); | |
336 | 341 | } |
337 | 342 | } |
338 | 343 | } |
349 | 354 | } |
350 | 355 | done = true; |
351 | 356 | cancelled = true; |
352 | return internalResume(Response.status(Response.Status.SERVICE_UNAVAILABLE).build()); | |
357 | try | |
358 | { | |
359 | return internalResume(Response.status(Response.Status.SERVICE_UNAVAILABLE).build()); | |
360 | } | |
361 | finally | |
362 | { | |
363 | nettyFlush(); | |
364 | } | |
353 | 365 | } |
354 | 366 | } |
355 | 367 | |
361 | 373 | if (done) return false; |
362 | 374 | done = true; |
363 | 375 | cancelled = true; |
364 | return internalResume(Response.status(Response.Status.SERVICE_UNAVAILABLE).header(HttpHeaders.RETRY_AFTER, retryAfter).build()); | |
365 | } | |
366 | } | |
367 | ||
368 | @Override | |
376 | try | |
377 | { | |
378 | return internalResume(Response.status(Response.Status.SERVICE_UNAVAILABLE).header(HttpHeaders.RETRY_AFTER, retryAfter).build()); | |
379 | } | |
380 | finally | |
381 | { | |
382 | nettyFlush(); | |
383 | } | |
384 | } | |
385 | } | |
386 | ||
387 | protected synchronized void nettyFlush() | |
388 | { | |
389 | flushed = true; | |
390 | ctx.writeAndFlush(nettyResponse.getDefaultFullHttpResponse()); | |
391 | ctx.close(); | |
392 | } | |
393 | ||
394 | @Override | |
369 | 395 | public boolean cancel(Date retryAfter) { |
370 | 396 | synchronized (responseLock) |
371 | 397 | { |
373 | 399 | if (done) return false; |
374 | 400 | done = true; |
375 | 401 | cancelled = true; |
376 | return internalResume(Response.status(Response.Status.SERVICE_UNAVAILABLE).header(HttpHeaders.RETRY_AFTER, retryAfter).build()); | |
402 | try | |
403 | { | |
404 | return internalResume(Response.status(Response.Status.SERVICE_UNAVAILABLE).header(HttpHeaders.RETRY_AFTER, retryAfter).build()); | |
405 | } | |
406 | finally | |
407 | { | |
408 | nettyFlush(); | |
409 | } | |
377 | 410 | } |
378 | 411 | } |
379 | 412 |
+9
-3
9 | 9 | import io.netty.handler.codec.http.HttpVersion; |
10 | 10 | import org.jboss.resteasy.specimpl.MultivaluedMapImpl; |
11 | 11 | import org.jboss.resteasy.spi.HttpResponse; |
12 | import org.jboss.resteasy.spi.ResteasyProviderFactory; | |
12 | 13 | |
13 | 14 | import javax.ws.rs.core.HttpHeaders; |
14 | 15 | import javax.ws.rs.core.MultivaluedMap; |
31 | 32 | private final ChannelHandlerContext ctx; |
32 | 33 | private boolean committed; |
33 | 34 | private boolean keepAlive; |
35 | private ResteasyProviderFactory providerFactory; | |
34 | 36 | |
35 | public NettyHttpResponse(ChannelHandlerContext ctx, boolean keepAlive) | |
37 | public NettyHttpResponse(ChannelHandlerContext ctx, boolean keepAlive, ResteasyProviderFactory providerFactory) | |
36 | 38 | { |
37 | 39 | outputHeaders = new MultivaluedMapImpl<String, Object>(); |
38 | 40 | byteBuf = ctx.alloc().buffer(); |
39 | 41 | os = new ByteBufOutputStream(byteBuf); |
40 | 42 | this.ctx = ctx; |
41 | 43 | this.keepAlive = keepAlive; |
44 | this.providerFactory = providerFactory; | |
42 | 45 | } |
43 | 46 | |
44 | 47 | @Override |
144 | 147 | byteBuf.retain(1); |
145 | 148 | } |
146 | 149 | |
147 | public DefaultFullHttpResponse getDefaultFullHttpResponse() { | |
150 | public DefaultFullHttpResponse getDefaultFullHttpResponse() | |
151 | { | |
148 | 152 | HttpResponseStatus status = HttpResponseStatus.valueOf(getStatus()); |
149 | return new DefaultFullHttpResponse(HTTP_1_1, status, getBuffer()); | |
153 | DefaultFullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, status, getBuffer()); | |
154 | RestEasyHttpResponseEncoder.transformHeaders(this, res, providerFactory); | |
155 | return res; | |
150 | 156 | } |
151 | 157 | } |
+179
-179
0 | package org.jboss.resteasy.plugins.server.netty; | |
1 | ||
2 | import io.netty.bootstrap.ServerBootstrap; | |
3 | import io.netty.channel.ChannelInitializer; | |
4 | import io.netty.channel.ChannelOption; | |
5 | import io.netty.channel.EventLoopGroup; | |
6 | import io.netty.channel.nio.NioEventLoopGroup; | |
7 | import io.netty.channel.socket.SocketChannel; | |
8 | import io.netty.channel.socket.nio.NioServerSocketChannel; | |
9 | import io.netty.handler.codec.http.HttpObjectAggregator; | |
10 | import io.netty.handler.codec.http.HttpRequestDecoder; | |
11 | import io.netty.handler.codec.http.HttpResponseEncoder; | |
12 | import io.netty.handler.ssl.SslHandler; | |
13 | import io.netty.util.concurrent.EventExecutor; | |
14 | import org.jboss.resteasy.core.SynchronousDispatcher; | |
15 | import org.jboss.resteasy.plugins.server.embedded.EmbeddedJaxrsServer; | |
16 | import org.jboss.resteasy.plugins.server.embedded.SecurityDomain; | |
17 | import org.jboss.resteasy.spi.ResteasyDeployment; | |
18 | ||
19 | import javax.net.ssl.SSLContext; | |
20 | import javax.net.ssl.SSLEngine; | |
21 | ||
22 | /** | |
23 | * An HTTP server that sends back the content of the received HTTP request | |
24 | * in a pretty plaintext form. | |
25 | * | |
26 | * @author <a href="http://www.jboss.org/netty/">The Netty Project</a> | |
27 | * @author Andy Taylor (andy.taylor@jboss.org) | |
28 | * @author <a href="http://gleamynode.net/">Trustin Lee</a> | |
29 | * @author Norman Maurer | |
30 | * @version $Rev: 2080 $, $Date: 2010-01-26 18:04:19 +0900 (Tue, 26 Jan 2010) $ | |
31 | */ | |
32 | public class NettyJaxrsServer implements EmbeddedJaxrsServer | |
33 | { | |
34 | protected ServerBootstrap bootstrap = new ServerBootstrap(); | |
35 | protected int port = 8080; | |
36 | protected ResteasyDeployment deployment = new ResteasyDeployment(); | |
37 | protected String root = ""; | |
38 | protected SecurityDomain domain; | |
39 | private EventLoopGroup eventLoopGroup; | |
40 | private EventLoopGroup eventExecutor; | |
41 | private int ioWorkerCount = Runtime.getRuntime().availableProcessors() * 2; | |
42 | private int executorThreadCount = 16; | |
43 | private SSLContext sslContext; | |
44 | private int maxRequestSize = 1024 * 1024 * 10; | |
45 | private int backlog = 128; | |
46 | ||
47 | public void setSSLContext(SSLContext sslContext) | |
48 | { | |
49 | this.sslContext = sslContext; | |
50 | } | |
51 | ||
52 | /** | |
53 | * Specify the worker count to use. For more information about this please see the javadocs of {@link EventLoopGroup} | |
54 | * | |
55 | * @param ioWorkerCount | |
56 | */ | |
57 | public void setIoWorkerCount(int ioWorkerCount) | |
58 | { | |
59 | this.ioWorkerCount = ioWorkerCount; | |
60 | } | |
61 | ||
62 | /** | |
63 | * Set the number of threads to use for the EventExecutor. For more information please see the javadocs of {@link EventExecutor}. | |
64 | * If you want to disable the use of the {@link EventExecutor} specify a value <= 0. This should only be done if you are 100% sure that you don't have any blocking | |
65 | * code in there. | |
66 | * | |
67 | * @param executorThreadCount | |
68 | */ | |
69 | public void setExecutorThreadCount(int executorThreadCount) | |
70 | { | |
71 | this.executorThreadCount = executorThreadCount; | |
72 | } | |
73 | ||
74 | /** | |
75 | * Set the max. request size in bytes. If this size is exceed we will send a "413 Request Entity Too Large" to the client. | |
76 | * | |
77 | * @param maxRequestSize the max request size. This is 10mb by default. | |
78 | */ | |
79 | public void setMaxRequestSize(int maxRequestSize) | |
80 | { | |
81 | this.maxRequestSize = maxRequestSize; | |
82 | } | |
83 | ||
84 | public int getPort() | |
85 | { | |
86 | return port; | |
87 | } | |
88 | ||
89 | public void setPort(int port) | |
90 | { | |
91 | this.port = port; | |
92 | } | |
93 | ||
94 | public void setBacklog(int backlog) | |
95 | { | |
96 | this.backlog = backlog; | |
97 | } | |
98 | ||
99 | @Override | |
100 | public void setDeployment(ResteasyDeployment deployment) | |
101 | { | |
102 | this.deployment = deployment; | |
103 | } | |
104 | ||
105 | @Override | |
106 | public void setRootResourcePath(String rootResourcePath) | |
107 | { | |
108 | root = rootResourcePath; | |
109 | if (root != null && root.equals("/")) root = ""; | |
110 | } | |
111 | ||
112 | @Override | |
113 | public ResteasyDeployment getDeployment() | |
114 | { | |
115 | return deployment; | |
116 | } | |
117 | ||
118 | @Override | |
119 | public void setSecurityDomain(SecurityDomain sc) | |
120 | { | |
121 | this.domain = sc; | |
122 | } | |
123 | ||
124 | @Override | |
125 | public void start() | |
126 | { | |
127 | eventLoopGroup = new NioEventLoopGroup(ioWorkerCount); | |
128 | eventExecutor = new NioEventLoopGroup(executorThreadCount); | |
129 | deployment.start(); | |
130 | final RequestDispatcher dispatcher = new RequestDispatcher((SynchronousDispatcher)deployment.getDispatcher(), deployment.getProviderFactory(), domain); | |
131 | // Configure the server. | |
132 | if (sslContext == null) { | |
133 | bootstrap.group(eventLoopGroup) | |
134 | .channel(NioServerSocketChannel.class) | |
135 | .childHandler(new ChannelInitializer<SocketChannel>() { | |
136 | @Override | |
137 | public void initChannel(SocketChannel ch) throws Exception { | |
138 | ch.pipeline().addLast(new HttpRequestDecoder()); | |
139 | ch.pipeline().addLast(new HttpObjectAggregator(maxRequestSize)); | |
140 | ch.pipeline().addLast(new HttpResponseEncoder()); | |
141 | ch.pipeline().addLast(new RestEasyHttpRequestDecoder(dispatcher.getDispatcher(), root, RestEasyHttpRequestDecoder.Protocol.HTTP)); | |
142 | ch.pipeline().addLast(new RestEasyHttpResponseEncoder(dispatcher)); | |
143 | ch.pipeline().addLast(eventExecutor, new RequestHandler(dispatcher)); | |
144 | } | |
145 | }) | |
146 | .option(ChannelOption.SO_BACKLOG, backlog) | |
147 | .childOption(ChannelOption.SO_KEEPALIVE, true); | |
148 | } else { | |
149 | final SSLEngine engine = sslContext.createSSLEngine(); | |
150 | engine.setUseClientMode(false); | |
151 | bootstrap.group(eventLoopGroup) | |
152 | .channel(NioServerSocketChannel.class) | |
153 | .childHandler(new ChannelInitializer<SocketChannel>() { | |
154 | @Override | |
155 | public void initChannel(SocketChannel ch) throws Exception { | |
156 | ch.pipeline().addFirst(new SslHandler(engine)); | |
157 | ch.pipeline().addLast(new HttpRequestDecoder()); | |
158 | ch.pipeline().addLast(new HttpObjectAggregator(maxRequestSize)); | |
159 | ch.pipeline().addLast(new HttpResponseEncoder()); | |
160 | ch.pipeline().addLast(new RestEasyHttpRequestDecoder(dispatcher.getDispatcher(), root, RestEasyHttpRequestDecoder.Protocol.HTTPS)); | |
161 | ch.pipeline().addLast(new RestEasyHttpResponseEncoder(dispatcher)); | |
162 | ch.pipeline().addLast(eventExecutor, new RequestHandler(dispatcher)); | |
163 | ||
164 | } | |
165 | }) | |
166 | .option(ChannelOption.SO_BACKLOG, backlog) | |
167 | .childOption(ChannelOption.SO_KEEPALIVE, true); | |
168 | } | |
169 | ||
170 | bootstrap.bind(port).syncUninterruptibly(); | |
171 | } | |
172 | ||
173 | @Override | |
174 | public void stop() | |
175 | { | |
176 | eventLoopGroup.shutdownGracefully(); | |
177 | eventExecutor.shutdownGracefully(); | |
178 | } | |
0 | package org.jboss.resteasy.plugins.server.netty; | |
1 | ||
2 | import io.netty.bootstrap.ServerBootstrap; | |
3 | import io.netty.channel.ChannelInitializer; | |
4 | import io.netty.channel.ChannelOption; | |
5 | import io.netty.channel.EventLoopGroup; | |
6 | import io.netty.channel.nio.NioEventLoopGroup; | |
7 | import io.netty.channel.socket.SocketChannel; | |
8 | import io.netty.channel.socket.nio.NioServerSocketChannel; | |
9 | import io.netty.handler.codec.http.HttpObjectAggregator; | |
10 | import io.netty.handler.codec.http.HttpRequestDecoder; | |
11 | import io.netty.handler.codec.http.HttpResponseEncoder; | |
12 | import io.netty.handler.ssl.SslHandler; | |
13 | import io.netty.util.concurrent.EventExecutor; | |
14 | import org.jboss.resteasy.core.SynchronousDispatcher; | |
15 | import org.jboss.resteasy.plugins.server.embedded.EmbeddedJaxrsServer; | |
16 | import org.jboss.resteasy.plugins.server.embedded.SecurityDomain; | |
17 | import org.jboss.resteasy.spi.ResteasyDeployment; | |
18 | ||
19 | import javax.net.ssl.SSLContext; | |
20 | import javax.net.ssl.SSLEngine; | |
21 | ||
22 | /** | |
23 | * An HTTP server that sends back the content of the received HTTP request | |
24 | * in a pretty plaintext form. | |
25 | * | |
26 | * @author <a href="http://www.jboss.org/netty/">The Netty Project</a> | |
27 | * @author Andy Taylor (andy.taylor@jboss.org) | |
28 | * @author <a href="http://gleamynode.net/">Trustin Lee</a> | |
29 | * @author Norman Maurer | |
30 | * @version $Rev: 2080 $, $Date: 2010-01-26 18:04:19 +0900 (Tue, 26 Jan 2010) $ | |
31 | */ | |
32 | public class NettyJaxrsServer implements EmbeddedJaxrsServer | |
33 | { | |
34 | protected ServerBootstrap bootstrap = new ServerBootstrap(); | |
35 | protected int port = 8080; | |
36 | protected ResteasyDeployment deployment = new ResteasyDeployment(); | |
37 | protected String root = ""; | |
38 | protected SecurityDomain domain; | |
39 | private EventLoopGroup eventLoopGroup; | |
40 | private EventLoopGroup eventExecutor; | |
41 | private int ioWorkerCount = Runtime.getRuntime().availableProcessors() * 2; | |
42 | private int executorThreadCount = 16; | |
43 | private SSLContext sslContext; | |
44 | private int maxRequestSize = 1024 * 1024 * 10; | |
45 | private int backlog = 128; | |
46 | ||
47 | public void setSSLContext(SSLContext sslContext) | |
48 | { | |
49 | this.sslContext = sslContext; | |
50 | } | |
51 | ||
52 | /** | |
53 | * Specify the worker count to use. For more information about this please see the javadocs of {@link EventLoopGroup} | |
54 | * | |
55 | * @param ioWorkerCount | |
56 | */ | |
57 | public void setIoWorkerCount(int ioWorkerCount) | |
58 | { | |
59 | this.ioWorkerCount = ioWorkerCount; | |
60 | } | |
61 | ||
62 | /** | |
63 | * Set the number of threads to use for the EventExecutor. For more information please see the javadocs of {@link EventExecutor}. | |
64 | * If you want to disable the use of the {@link EventExecutor} specify a value <= 0. This should only be done if you are 100% sure that you don't have any blocking | |
65 | * code in there. | |
66 | * | |
67 | * @param executorThreadCount | |
68 | */ | |
69 | public void setExecutorThreadCount(int executorThreadCount) | |
70 | { | |
71 | this.executorThreadCount = executorThreadCount; | |
72 | } | |
73 | ||
74 | /** | |
75 | * Set the max. request size in bytes. If this size is exceed we will send a "413 Request Entity Too Large" to the client. | |
76 | * | |
77 | * @param maxRequestSize the max request size. This is 10mb by default. | |
78 | */ | |
79 | public void setMaxRequestSize(int maxRequestSize) | |
80 | { | |
81 | this.maxRequestSize = maxRequestSize; | |
82 | } | |
83 | ||
84 | public int getPort() | |
85 | { | |
86 | return port; | |
87 | } | |
88 | ||
89 | public void setPort(int port) | |
90 | { | |
91 | this.port = port; | |
92 | } | |
93 | ||
94 | public void setBacklog(int backlog) | |
95 | { | |
96 | this.backlog = backlog; | |
97 | } | |
98 | ||
99 | @Override | |
100 | public void setDeployment(ResteasyDeployment deployment) | |
101 | { | |
102 | this.deployment = deployment; | |
103 | } | |
104 | ||
105 | @Override | |
106 | public void setRootResourcePath(String rootResourcePath) | |
107 | { | |
108 | root = rootResourcePath; | |
109 | if (root != null && root.equals("/")) root = ""; | |
110 | } | |
111 | ||
112 | @Override | |
113 | public ResteasyDeployment getDeployment() | |
114 | { | |
115 | return deployment; | |
116 | } | |
117 | ||
118 | @Override | |
119 | public void setSecurityDomain(SecurityDomain sc) | |
120 | { | |
121 | this.domain = sc; | |
122 | } | |
123 | ||
124 | @Override | |
125 | public void start() | |
126 | { | |
127 | eventLoopGroup = new NioEventLoopGroup(ioWorkerCount); | |
128 | eventExecutor = new NioEventLoopGroup(executorThreadCount); | |
129 | deployment.start(); | |
130 | final RequestDispatcher dispatcher = new RequestDispatcher((SynchronousDispatcher)deployment.getDispatcher(), deployment.getProviderFactory(), domain); | |
131 | // Configure the server. | |
132 | if (sslContext == null) { | |
133 | bootstrap.group(eventLoopGroup) | |
134 | .channel(NioServerSocketChannel.class) | |
135 | .childHandler(new ChannelInitializer<SocketChannel>() { | |
136 | @Override | |
137 | public void initChannel(SocketChannel ch) throws Exception { | |
138 | ch.pipeline().addLast(new HttpRequestDecoder()); | |
139 | ch.pipeline().addLast(new HttpObjectAggregator(maxRequestSize)); | |
140 | ch.pipeline().addLast(new HttpResponseEncoder()); | |
141 | ch.pipeline().addLast(new RestEasyHttpRequestDecoder(dispatcher.getDispatcher(), root, RestEasyHttpRequestDecoder.Protocol.HTTP)); | |
142 | ch.pipeline().addLast(new RestEasyHttpResponseEncoder(dispatcher)); | |
143 | ch.pipeline().addLast(eventExecutor, new RequestHandler(dispatcher)); | |
144 | } | |
145 | }) | |
146 | .option(ChannelOption.SO_BACKLOG, backlog) | |
147 | .childOption(ChannelOption.SO_KEEPALIVE, true); | |
148 | } else { | |
149 | final SSLEngine engine = sslContext.createSSLEngine(); | |
150 | engine.setUseClientMode(false); | |
151 | bootstrap.group(eventLoopGroup) | |
152 | .channel(NioServerSocketChannel.class) | |
153 | .childHandler(new ChannelInitializer<SocketChannel>() { | |
154 | @Override | |
155 | public void initChannel(SocketChannel ch) throws Exception { | |
156 | ch.pipeline().addFirst(new SslHandler(engine)); | |
157 | ch.pipeline().addLast(new HttpRequestDecoder()); | |
158 | ch.pipeline().addLast(new HttpObjectAggregator(maxRequestSize)); | |
159 | ch.pipeline().addLast(new HttpResponseEncoder()); | |
160 | ch.pipeline().addLast(new RestEasyHttpRequestDecoder(dispatcher.getDispatcher(), root, RestEasyHttpRequestDecoder.Protocol.HTTPS)); | |
161 | ch.pipeline().addLast(new RestEasyHttpResponseEncoder(dispatcher)); | |
162 | ch.pipeline().addLast(eventExecutor, new RequestHandler(dispatcher)); | |
163 | ||
164 | } | |
165 | }) | |
166 | .option(ChannelOption.SO_BACKLOG, backlog) | |
167 | .childOption(ChannelOption.SO_KEEPALIVE, true); | |
168 | } | |
169 | ||
170 | bootstrap.bind(port).syncUninterruptibly(); | |
171 | } | |
172 | ||
173 | @Override | |
174 | public void stop() | |
175 | { | |
176 | eventLoopGroup.shutdownGracefully(); | |
177 | eventExecutor.shutdownGracefully(); | |
178 | } | |
179 | 179 | }⏎ |
+2
-2
71 | 71 | future = ctx.writeAndFlush(response); |
72 | 72 | } else { |
73 | 73 | // Write an empty response |
74 | future = ctx.write(response); | |
74 | //future = ctx.write(response); | |
75 | 75 | // retain buffer since it was automatically |
76 | 76 | // reference counted by the write operation above |
77 | response.retain(); | |
77 | //response.retain(); | |
78 | 78 | } |
79 | 79 | // Close the non-keep-alive connection after the write operation is done. |
80 | 80 | if (!request.isKeepAlive()) |
+1
-1
56 | 56 | protected void decode(ChannelHandlerContext ctx, io.netty.handler.codec.http.HttpRequest request, List<Object> out) throws Exception |
57 | 57 | { |
58 | 58 | boolean keepAlive = HttpHeaders.isKeepAlive(request); |
59 | final NettyHttpResponse response = new NettyHttpResponse(ctx, keepAlive); | |
59 | final NettyHttpResponse response = new NettyHttpResponse(ctx, keepAlive, dispatcher.getProviderFactory()); | |
60 | 60 | final ResteasyHttpHeaders headers; |
61 | 61 | final ResteasyUriInfo uriInfo; |
62 | 62 | try |
+29
-26
6 | 6 | import io.netty.handler.codec.http.HttpHeaders.Names; |
7 | 7 | import io.netty.handler.codec.http.HttpHeaders.Values; |
8 | 8 | import io.netty.handler.codec.http.HttpResponse; |
9 | import org.jboss.resteasy.spi.ResteasyProviderFactory; | |
9 | 10 | |
10 | 11 | import javax.ws.rs.ext.RuntimeDelegate; |
11 | 12 | import java.util.List; |
38 | 39 | protected void encode(ChannelHandlerContext ctx, NettyHttpResponse nettyResponse, List<Object> out) throws Exception |
39 | 40 | { |
40 | 41 | ByteBuf buffer = nettyResponse.getBuffer(); |
41 | if (buffer.readableBytes() == 0) { | |
42 | // content not written yet by the AsyncResponse | |
43 | return; | |
44 | } | |
45 | 42 | HttpResponse response = nettyResponse.getDefaultFullHttpResponse(); |
46 | 43 | |
47 | for (Map.Entry<String, List<Object>> entry : nettyResponse.getOutputHeaders().entrySet()) | |
48 | { | |
49 | String key = entry.getKey(); | |
50 | for (Object value : entry.getValue()) | |
51 | { | |
52 | RuntimeDelegate.HeaderDelegate delegate = dispatcher.providerFactory.getHeaderDelegate(value.getClass()); | |
53 | if (delegate != null) | |
54 | { | |
55 | response.headers().add(key, delegate.toString(value)); | |
56 | } | |
57 | else | |
58 | { | |
59 | response.headers().set(key, value.toString()); | |
60 | } | |
61 | } | |
62 | } | |
44 | transformHeaders(nettyResponse, response, dispatcher.providerFactory); | |
63 | 45 | |
64 | if (nettyResponse.isKeepAlive()) | |
65 | { | |
66 | // Add content length and connection header if needed | |
67 | response.headers().set(Names.CONTENT_LENGTH, buffer.readableBytes()); | |
68 | response.headers().set(Names.CONNECTION, Values.KEEP_ALIVE); | |
69 | } | |
70 | 46 | out.add(response); |
71 | 47 | } |
72 | 48 | |
49 | public static void transformHeaders(NettyHttpResponse nettyResponse, HttpResponse response, ResteasyProviderFactory factory) | |
50 | { | |
51 | ByteBuf buffer = nettyResponse.getBuffer(); | |
52 | if (nettyResponse.isKeepAlive()) | |
53 | { | |
54 | // Add content length and connection header if needed | |
55 | response.headers().set(Names.CONTENT_LENGTH, buffer.readableBytes()); | |
56 | response.headers().set(Names.CONNECTION, Values.KEEP_ALIVE); | |
57 | } | |
58 | for (Map.Entry<String, List<Object>> entry : nettyResponse.getOutputHeaders().entrySet()) | |
59 | { | |
60 | String key = entry.getKey(); | |
61 | for (Object value : entry.getValue()) | |
62 | { | |
63 | RuntimeDelegate.HeaderDelegate delegate = factory.getHeaderDelegate(value.getClass()); | |
64 | if (delegate != null) | |
65 | { | |
66 | response.headers().add(key, delegate.toString(value)); | |
67 | } | |
68 | else | |
69 | { | |
70 | response.headers().set(key, value.toString()); | |
71 | } | |
72 | } | |
73 | } | |
74 | } | |
75 | ||
73 | 76 | } |
+200
-0
0 | package org.jboss.resteasy.test; | |
1 | ||
2 | ||
3 | import javax.ws.rs.ForbiddenException; | |
4 | import javax.ws.rs.GET; | |
5 | import javax.ws.rs.PUT; | |
6 | import javax.ws.rs.Path; | |
7 | import javax.ws.rs.PathParam; | |
8 | import javax.ws.rs.Produces; | |
9 | import javax.ws.rs.container.AsyncResponse; | |
10 | import javax.ws.rs.container.Suspended; | |
11 | import javax.ws.rs.core.MediaType; | |
12 | import javax.ws.rs.core.Response; | |
13 | import java.util.concurrent.CountDownLatch; | |
14 | import java.util.concurrent.TimeUnit; | |
15 | ||
16 | /** | |
17 | * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> | |
18 | * @version $Revision: 1 $ | |
19 | */ | |
20 | @Path("/jaxrs") | |
21 | public class AsyncJaxrsResource | |
22 | { | |
23 | protected boolean cancelled; | |
24 | ||
25 | @GET | |
26 | @Path("resume/object") | |
27 | @Produces("application/xml") | |
28 | public void resumeObject(@Suspended final AsyncResponse response) { | |
29 | response.resume(new XmlData("bill")); | |
30 | } | |
31 | ||
32 | @GET | |
33 | @Path("resume/object/thread") | |
34 | @Produces("application/xml") | |
35 | public void resumeObjectThread(@Suspended final AsyncResponse response) throws Exception | |
36 | { | |
37 | Thread t = new Thread() | |
38 | { | |
39 | @Override | |
40 | public void run() | |
41 | { | |
42 | response.resume(new XmlData("bill")); | |
43 | } | |
44 | }; | |
45 | t.start(); | |
46 | } | |
47 | ||
48 | ||
49 | ||
50 | @GET | |
51 | @Path("injection-failure/{param}") | |
52 | public void injectionFailure(@Suspended final AsyncResponse response, @PathParam("param") int id) { | |
53 | System.out.println("injectionFailure: " + id); | |
54 | throw new ForbiddenException("Should be unreachable"); | |
55 | } | |
56 | ||
57 | @GET | |
58 | @Path("method-failure") | |
59 | public void injectionFailure(@Suspended final AsyncResponse response) { | |
60 | throw new ForbiddenException("Should be unreachable"); | |
61 | } | |
62 | ||
63 | ||
64 | ||
65 | @GET | |
66 | @Path("cancelled") | |
67 | public Response getCancelled() | |
68 | { | |
69 | System.out.println("getCancelled called!"); | |
70 | if (cancelled) return Response.noContent().build(); | |
71 | else return Response.status(500).build(); | |
72 | } | |
73 | ||
74 | @PUT | |
75 | @Path("cancelled") | |
76 | public void resetCancelled() | |
77 | { | |
78 | cancelled = false; | |
79 | ||
80 | } | |
81 | ||
82 | @GET | |
83 | @Produces("text/plain") | |
84 | public void get(@Suspended final AsyncResponse response) throws Exception | |
85 | { | |
86 | response.setTimeout(200000, TimeUnit.MILLISECONDS); | |
87 | Thread t = new Thread() | |
88 | { | |
89 | @Override | |
90 | public void run() | |
91 | { | |
92 | try | |
93 | { | |
94 | System.out.println("STARTED!!!!"); | |
95 | Thread.sleep(100); | |
96 | Response jaxrs = Response.ok("hello").type(MediaType.TEXT_PLAIN).build(); | |
97 | response.resume(jaxrs); | |
98 | } | |
99 | catch (Exception e) | |
100 | { | |
101 | e.printStackTrace(); | |
102 | } | |
103 | } | |
104 | }; | |
105 | t.start(); | |
106 | } | |
107 | ||
108 | @GET | |
109 | @Path("empty") | |
110 | @Produces("text/plain") | |
111 | public void getEmpty(@Suspended final AsyncResponse response) throws Exception | |
112 | { | |
113 | response.setTimeout(200000, TimeUnit.MILLISECONDS); | |
114 | Thread t = new Thread() | |
115 | { | |
116 | @Override | |
117 | public void run() | |
118 | { | |
119 | try | |
120 | { | |
121 | System.out.println("STARTED Empty!!!!"); | |
122 | Thread.sleep(100); | |
123 | response.resume(Response.noContent().build()); | |
124 | } | |
125 | catch (Exception e) | |
126 | { | |
127 | e.printStackTrace(); | |
128 | } | |
129 | } | |
130 | }; | |
131 | t.start(); | |
132 | } | |
133 | ||
134 | ||
135 | @GET | |
136 | @Path("timeout") | |
137 | @Produces("text/plain") | |
138 | public void timeout(@Suspended final AsyncResponse response) | |
139 | { | |
140 | response.setTimeout(10, TimeUnit.MILLISECONDS); | |
141 | Thread t = new Thread() | |
142 | { | |
143 | @Override | |
144 | public void run() | |
145 | { | |
146 | try | |
147 | { | |
148 | System.out.println("STARTED!!!!"); | |
149 | Thread.sleep(100000); | |
150 | Response jaxrs = Response.ok("goodbye").type(MediaType.TEXT_PLAIN).build(); | |
151 | response.resume(jaxrs); | |
152 | } | |
153 | catch (Exception e) | |
154 | { | |
155 | e.printStackTrace(); | |
156 | } | |
157 | } | |
158 | }; | |
159 | t.start(); | |
160 | } | |
161 | ||
162 | ||
163 | @GET | |
164 | @Path("cancel") | |
165 | @Produces("text/plain") | |
166 | public void cancel(@Suspended final AsyncResponse response) throws Exception | |
167 | { | |
168 | response.setTimeout(10000, TimeUnit.MILLISECONDS); | |
169 | final CountDownLatch sync = new CountDownLatch(1); | |
170 | final CountDownLatch ready = new CountDownLatch(1); | |
171 | Thread t = new Thread() | |
172 | { | |
173 | @Override | |
174 | public void run() | |
175 | { | |
176 | try | |
177 | { | |
178 | sync.countDown(); | |
179 | System.out.println("cancel awaiting thread"); | |
180 | ready.await(); | |
181 | System.out.println("cancel resuming"); | |
182 | Response jaxrs = Response.ok("hello").type(MediaType.TEXT_PLAIN).build(); | |
183 | cancelled = !response.resume(jaxrs); | |
184 | } | |
185 | catch (Exception e) | |
186 | { | |
187 | e.printStackTrace(); | |
188 | } | |
189 | } | |
190 | }; | |
191 | t.start(); | |
192 | ||
193 | sync.await(); | |
194 | System.out.println("Cancelling..."); | |
195 | response.cancel(); | |
196 | ready.countDown(); | |
197 | } | |
198 | ||
199 | }⏎ |
+201
-0
0 | package org.jboss.resteasy.test; | |
1 | ||
2 | import org.junit.AfterClass; | |
3 | import org.junit.Assert; | |
4 | import org.junit.BeforeClass; | |
5 | import org.junit.Test; | |
6 | ||
7 | import javax.ws.rs.ForbiddenException; | |
8 | import javax.ws.rs.NotFoundException; | |
9 | import javax.ws.rs.client.Client; | |
10 | import javax.ws.rs.client.ClientBuilder; | |
11 | import javax.ws.rs.core.Response; | |
12 | ||
13 | import static org.jboss.resteasy.test.TestPortProvider.generateURL; | |
14 | ||
15 | /** | |
16 | * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> | |
17 | * @version $Revision: 1 $ | |
18 | */ | |
19 | public class JaxrsAsyncTest | |
20 | { | |
21 | static String BASE_URI = generateURL(""); | |
22 | static Client client; | |
23 | ||
24 | @BeforeClass | |
25 | public static void setup() throws Exception | |
26 | { | |
27 | NettyContainer.start().getRegistry().addSingletonResource(new AsyncJaxrsResource()); | |
28 | client = ClientBuilder.newClient(); | |
29 | } | |
30 | ||
31 | @AfterClass | |
32 | public static void end() throws Exception | |
33 | { | |
34 | client.close(); | |
35 | NettyContainer.stop(); | |
36 | } | |
37 | ||
38 | @Test | |
39 | public void testInjectionFailure() throws Exception | |
40 | { | |
41 | System.out.println("***INJECTION FAILURE***"); | |
42 | long start = System.currentTimeMillis(); | |
43 | Client client = ClientBuilder.newClient(); | |
44 | Response response = client.target(BASE_URI).path("jaxrs/injection-failure/abcd").request().get(); | |
45 | Assert.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); | |
46 | long end = System.currentTimeMillis() - start; | |
47 | Assert.assertTrue(end < 1000); // should take less than 1 second | |
48 | response.close(); | |
49 | client.close(); | |
50 | } | |
51 | ||
52 | @Test | |
53 | public void testMethodFailure() throws Exception | |
54 | { | |
55 | System.out.println("***method FAILURE***"); | |
56 | long start = System.currentTimeMillis(); | |
57 | Client client = ClientBuilder.newClient(); | |
58 | Response response = client.target(BASE_URI).path("jaxrs/method-failure").request().get(); | |
59 | Assert.assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus()); | |
60 | long end = System.currentTimeMillis() - start; | |
61 | Assert.assertTrue(end < 1000); // should take less than 1 second | |
62 | response.close(); | |
63 | client.close(); | |
64 | } | |
65 | ||
66 | ||
67 | ||
68 | @Test | |
69 | public void testAsync() throws Exception | |
70 | { | |
71 | Client client = ClientBuilder.newClient(); | |
72 | callAsync(client); | |
73 | callAsync(client); | |
74 | callAsync(client); | |
75 | client.close(); | |
76 | } | |
77 | ||
78 | private void callAsync(Client client) | |
79 | { | |
80 | long start = System.currentTimeMillis(); | |
81 | Response response = client.target(BASE_URI).path("jaxrs").request().get(); | |
82 | long end = System.currentTimeMillis() - start; | |
83 | Assert.assertEquals(200, response.getStatus()); | |
84 | System.out.println(response.getHeaders().size()); | |
85 | System.out.println(response.getHeaders().keySet().iterator().next()); | |
86 | Assert.assertEquals("hello", response.readEntity(String.class)); | |
87 | Assert.assertTrue(end < 1000); // should take less than 1 second | |
88 | response.close(); | |
89 | } | |
90 | ||
91 | @Test | |
92 | public void testEmpty() throws Exception | |
93 | { | |
94 | Client client = ClientBuilder.newClient(); | |
95 | callEmpty(client); | |
96 | callEmpty(client); | |
97 | callEmpty(client); | |
98 | client.close(); | |
99 | } | |
100 | ||
101 | private void callEmpty(Client client) | |
102 | { | |
103 | long start = System.currentTimeMillis(); | |
104 | Response response = client.target(BASE_URI).path("jaxrs/empty").request().get(); | |
105 | long end = System.currentTimeMillis() - start; | |
106 | Assert.assertEquals(204, response.getStatus()); | |
107 | Assert.assertTrue(end < 1000); // should take less than 1 second | |
108 | response.close(); | |
109 | } | |
110 | ||
111 | ||
112 | @Test | |
113 | public void testTimeout() throws Exception | |
114 | { | |
115 | Client client = ClientBuilder.newClient(); | |
116 | Response response = client.target(BASE_URI).path("jaxrs/timeout").request().get(); | |
117 | Assert.assertEquals(503, response.getStatus()); | |
118 | response.close(); | |
119 | client.close(); | |
120 | } | |
121 | ||
122 | @Test | |
123 | public void testCancelled() throws Exception | |
124 | { | |
125 | Client client = ClientBuilder.newClient(); | |
126 | Response response = null; | |
127 | System.out.println("calling cancelled"); | |
128 | response = client.target(BASE_URI).path("jaxrs/cancelled").request().put(null); | |
129 | Assert.assertEquals(204, response.getStatus()); | |
130 | response.close(); | |
131 | response = client.target(BASE_URI).path("jaxrs/cancelled").request().get(); | |
132 | System.out.println("returned from calling cancelled"); | |
133 | Assert.assertEquals(500, response.getStatus()); | |
134 | System.out.println("done"); | |
135 | ||
136 | response.close(); | |
137 | client.close(); | |
138 | } | |
139 | ||
140 | ||
141 | @Test | |
142 | public void testCancel() throws Exception | |
143 | { | |
144 | Client client = ClientBuilder.newClient(); | |
145 | Response response = null; | |
146 | System.out.println("calling cancelled"); | |
147 | response = client.target(BASE_URI).path("jaxrs/cancelled").request().put(null); | |
148 | Assert.assertEquals(204, response.getStatus()); | |
149 | response.close(); | |
150 | response = client.target(BASE_URI).path("jaxrs/cancelled").request().get(); | |
151 | System.out.println("returned from calling cancelled"); | |
152 | Assert.assertEquals(500, response.getStatus()); | |
153 | System.out.println("done"); | |
154 | response.close(); | |
155 | ||
156 | System.out.println("calling cancel"); | |
157 | response = client.target(BASE_URI).path("jaxrs/cancel").request().get(); | |
158 | System.out.println("got response"); | |
159 | Assert.assertEquals(503, response.getStatus()); | |
160 | response.close(); | |
161 | System.out.println("calling cancelled"); | |
162 | response = client.target(BASE_URI).path("jaxrs/cancelled").request().get(); | |
163 | System.out.println("returned from calling cancelled"); | |
164 | Assert.assertEquals(204, response.getStatus()); | |
165 | System.out.println("done"); | |
166 | ||
167 | response.close(); | |
168 | client.close(); | |
169 | } | |
170 | ||
171 | @Test | |
172 | public void testResumeObject() throws Exception | |
173 | { | |
174 | Client client = ClientBuilder.newClient(); | |
175 | long start = System.currentTimeMillis(); | |
176 | Response response = client.target(BASE_URI).path("jaxrs/resume/object").request().get(); | |
177 | long end = System.currentTimeMillis() - start; | |
178 | Assert.assertEquals(200, response.getStatus()); | |
179 | Assert.assertEquals("bill", response.readEntity(XmlData.class).getName()); | |
180 | Assert.assertTrue(end < 1000); // should take less than 1 second | |
181 | response.close(); | |
182 | client.close(); | |
183 | } | |
184 | ||
185 | @Test | |
186 | public void testResumeObjectThread() throws Exception | |
187 | { | |
188 | Client client = ClientBuilder.newClient(); | |
189 | long start = System.currentTimeMillis(); | |
190 | Response response = client.target(BASE_URI).path("jaxrs/resume/object/thread").request().get(); | |
191 | long end = System.currentTimeMillis() - start; | |
192 | Assert.assertEquals(200, response.getStatus()); | |
193 | Assert.assertEquals("bill", response.readEntity(XmlData.class).getName()); | |
194 | Assert.assertTrue(end < 1000); // should take less than 1 second | |
195 | response.close(); | |
196 | client.close(); | |
197 | } | |
198 | ||
199 | ||
200 | } |
+56
-0
0 | package org.jboss.resteasy.test; | |
1 | ||
2 | import org.jboss.resteasy.client.jaxrs.ResteasyClient; | |
3 | import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; | |
4 | import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget; | |
5 | import org.junit.AfterClass; | |
6 | import org.junit.Assert; | |
7 | import org.junit.BeforeClass; | |
8 | import org.junit.Test; | |
9 | ||
10 | import javax.ws.rs.GET; | |
11 | import javax.ws.rs.Path; | |
12 | import javax.ws.rs.Produces; | |
13 | ||
14 | import static org.jboss.resteasy.test.TestPortProvider.generateURL; | |
15 | ||
16 | /** | |
17 | * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> | |
18 | * @version $Revision: 1 $ | |
19 | */ | |
20 | public class NettyTest | |
21 | { | |
22 | @Path("/test") | |
23 | public static class Resource | |
24 | { | |
25 | @GET | |
26 | @Produces("text/plain") | |
27 | public String hello() | |
28 | { | |
29 | return "hello world"; | |
30 | } | |
31 | ||
32 | ||
33 | } | |
34 | ||
35 | @BeforeClass | |
36 | public static void setup() throws Exception | |
37 | { | |
38 | NettyContainer.start().getRegistry().addPerRequestResource(Resource.class); | |
39 | } | |
40 | ||
41 | @AfterClass | |
42 | public static void end() throws Exception | |
43 | { | |
44 | NettyContainer.stop(); | |
45 | } | |
46 | ||
47 | @Test | |
48 | public void testBasic() throws Exception | |
49 | { | |
50 | ResteasyClient client = new ResteasyClientBuilder().build(); | |
51 | ResteasyWebTarget target = client.target(generateURL("/test")); | |
52 | String val = target.request().get(String.class); | |
53 | Assert.assertEquals(val, "hello world"); | |
54 | } | |
55 | } |
+34
-0
0 | package org.jboss.resteasy.test; | |
1 | ||
2 | import javax.xml.bind.annotation.XmlRootElement; | |
3 | ||
4 | /** | |
5 | * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> | |
6 | * @version $Revision: 1 $ | |
7 | */ | |
8 | @XmlRootElement(name = "data") | |
9 | public class XmlData | |
10 | { | |
11 | protected String name; | |
12 | ||
13 | public XmlData(String data) | |
14 | { | |
15 | this.name = data; | |
16 | } | |
17 | ||
18 | public XmlData() | |
19 | { | |
20 | } | |
21 | ||
22 | public String getName() | |
23 | { | |
24 | return name; | |
25 | } | |
26 | ||
27 | public void setName(String name) | |
28 | { | |
29 | this.name = name; | |
30 | } | |
31 | ||
32 | ||
33 | } |