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.client5.testing.async;
28
29 import java.io.IOException;
30 import java.nio.ByteBuffer;
31 import java.util.List;
32 import java.util.concurrent.atomic.AtomicReference;
33
34 import org.apache.hc.client5.http.auth.StandardAuthScheme;
35 import org.apache.hc.client5.testing.auth.Authenticator;
36 import org.apache.hc.client5.testing.auth.BasicAuthTokenExtractor;
37 import org.apache.hc.core5.http.ContentType;
38 import org.apache.hc.core5.http.EntityDetails;
39 import org.apache.hc.core5.http.Header;
40 import org.apache.hc.core5.http.HttpException;
41 import org.apache.hc.core5.http.HttpHeaders;
42 import org.apache.hc.core5.http.HttpRequest;
43 import org.apache.hc.core5.http.HttpResponse;
44 import org.apache.hc.core5.http.HttpStatus;
45 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
46 import org.apache.hc.core5.http.message.BasicHttpResponse;
47 import org.apache.hc.core5.http.nio.AsyncResponseProducer;
48 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
49 import org.apache.hc.core5.http.nio.CapacityChannel;
50 import org.apache.hc.core5.http.nio.DataStreamChannel;
51 import org.apache.hc.core5.http.nio.ResponseChannel;
52 import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer;
53 import org.apache.hc.core5.http.nio.support.BasicResponseProducer;
54 import org.apache.hc.core5.http.protocol.HttpContext;
55 import org.apache.hc.core5.net.URIAuthority;
56 import org.apache.hc.core5.util.Args;
57
58 public class AuthenticatingAsyncDecorator implements AsyncServerExchangeHandler {
59
60 private final AsyncServerExchangeHandler exchangeHandler;
61 private final Authenticator authenticator;
62 private final AtomicReference<AsyncResponseProducer> responseProducerRef;
63 private final BasicAuthTokenExtractor authTokenExtractor;
64
65 public AuthenticatingAsyncDecorator(final AsyncServerExchangeHandler exchangeHandler, final Authenticator authenticator) {
66 this.exchangeHandler = Args.notNull(exchangeHandler, "Request handler");
67 this.authenticator = Args.notNull(authenticator, "Authenticator");
68 this.responseProducerRef = new AtomicReference<>();
69 this.authTokenExtractor = new BasicAuthTokenExtractor();
70 }
71
72 protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
73 }
74
75 @Override
76 public void handleRequest(
77 final HttpRequest request,
78 final EntityDetails entityDetails,
79 final ResponseChannel responseChannel,
80 final HttpContext context) throws HttpException, IOException {
81 final Header h = request.getFirstHeader(HttpHeaders.AUTHORIZATION);
82 final String challengeResponse = h != null ? authTokenExtractor.extract(h.getValue()) : null;
83
84 final URIAuthority authority = request.getAuthority();
85 final String requestUri = request.getRequestUri();
86
87 final boolean authenticated = authenticator.authenticate(authority, requestUri, challengeResponse);
88 final Header expect = request.getFirstHeader(HttpHeaders.EXPECT);
89 final boolean expectContinue = expect != null && "100-continue".equalsIgnoreCase(expect.getValue());
90
91 if (authenticated) {
92 if (expectContinue) {
93 responseChannel.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE), context);
94 }
95 exchangeHandler.handleRequest(request, entityDetails, responseChannel, context);
96 } else {
97 final HttpResponse unauthorized = new BasicHttpResponse(HttpStatus.SC_UNAUTHORIZED);
98 final String realm = authenticator.getRealm(authority, requestUri);
99 unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=\"" + realm + "\"");
100
101 customizeUnauthorizedResponse(unauthorized);
102
103 final AsyncResponseProducer responseProducer = new BasicResponseProducer(
104 unauthorized,
105 new BasicAsyncEntityProducer("Unauthorized", ContentType.TEXT_PLAIN));
106 responseProducerRef.set(responseProducer);
107 responseProducer.sendResponse(responseChannel, context);
108 }
109
110 }
111
112 @Override
113 public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
114 final AsyncResponseProducer responseProducer = responseProducerRef.get();
115 if (responseProducer == null) {
116 exchangeHandler.updateCapacity(capacityChannel);
117 } else {
118 capacityChannel.update(Integer.MAX_VALUE);
119 }
120 }
121
122 @Override
123 public final void consume(final ByteBuffer src) throws IOException {
124 final AsyncResponseProducer responseProducer = responseProducerRef.get();
125 if (responseProducer == null) {
126 exchangeHandler.consume(src);
127 }
128 }
129
130 @Override
131 public final void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
132 final AsyncResponseProducer responseProducer = responseProducerRef.get();
133 if (responseProducer == null) {
134 exchangeHandler.streamEnd(trailers);
135 }
136 }
137
138 @Override
139 public final int available() {
140 final AsyncResponseProducer responseProducer = responseProducerRef.get();
141 if (responseProducer == null) {
142 return exchangeHandler.available();
143 } else {
144 return responseProducer.available();
145 }
146 }
147
148 @Override
149 public final void produce(final DataStreamChannel channel) throws IOException {
150 final AsyncResponseProducer responseProducer = responseProducerRef.get();
151 if (responseProducer == null) {
152 exchangeHandler.produce(channel);
153 } else {
154 responseProducer.produce(channel);
155 }
156 }
157
158 @Override
159 public final void failed(final Exception cause) {
160 try {
161 exchangeHandler.failed(cause);
162 final AsyncResponseProducer dataProducer = responseProducerRef.getAndSet(null);
163 if (dataProducer != null) {
164 dataProducer.failed(cause);
165 }
166 } finally {
167 releaseResources();
168 }
169 }
170
171 @Override
172 public final void releaseResources() {
173 exchangeHandler.releaseResources();
174 final AsyncResponseProducer dataProducer = responseProducerRef.getAndSet(null);
175 if (dataProducer != null) {
176 dataProducer.releaseResources();
177 }
178 }
179
180 }