001/* 002 * This file is part of the JDrupes non-blocking HTTP Codec 003 * Copyright (C) 2016, 2017 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as published 007 * by the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 013 * License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.httpcodec.protocols.http.client; 020 021import java.nio.Buffer; 022import java.nio.ByteBuffer; 023import java.time.Instant; 024import java.util.Optional; 025import java.util.ServiceLoader; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028import java.util.stream.StreamSupport; 029 030import org.jdrupes.httpcodec.Codec; 031import org.jdrupes.httpcodec.Decoder; 032import org.jdrupes.httpcodec.Encoder; 033import org.jdrupes.httpcodec.ProtocolException; 034import org.jdrupes.httpcodec.plugin.UpgradeProvider; 035 036import static org.jdrupes.httpcodec.protocols.http.HttpConstants.*; 037import org.jdrupes.httpcodec.protocols.http.HttpConstants.HttpStatus; 038import org.jdrupes.httpcodec.protocols.http.HttpDecoder; 039import org.jdrupes.httpcodec.protocols.http.HttpField; 040import org.jdrupes.httpcodec.protocols.http.HttpProtocolException; 041import org.jdrupes.httpcodec.protocols.http.HttpRequest; 042import org.jdrupes.httpcodec.protocols.http.HttpResponse; 043import org.jdrupes.httpcodec.types.Converters; 044import org.jdrupes.httpcodec.types.StringList; 045 046/** 047 * A decoder for HTTP reponses. It accepts data from a sequence of 048 * {@link ByteBuffer}s and decodes them into {@link HttpResponse}s 049 * and their (optional) payload. 050 * 051 * ![HttpResponseDecoder](httpresponsedecoder.svg) 052 * 053 * Headers 054 * ------- 055 * 056 * The decoder converts a `Retry-After` header with a delay value to 057 * a header with a date by adding the span to the time in the `Date` header. 058 * 059 * 060 * @see "[RFC 7231, Section 7.1.3](https://tools.ietf.org/html/rfc7231#section-7.1.3)" 061 * 062 * @startuml httpresponsedecoder.svg 063 * class HttpResponseDecoder { 064 * +HttpRequestDecoder() 065 * +Result decode(ByteBuffer in, Buffer out, boolean endOfInput) 066 * } 067 * 068 * class HttpDecoder<T extends HttpMessageHeader, R extends HttpMessageHeader> { 069 * } 070 * 071 * HttpDecoder <|-- HttpResponseDecoder: <<bind>> <T -> HttpResponse, R -> HttpRequest> 072 * @enduml 073 */ 074public class HttpResponseDecoder 075 extends HttpDecoder<HttpResponse, HttpRequest> 076 implements Decoder<HttpResponse, HttpRequest> { 077 078 // RFC 7230 3.1.2 079 private static final Pattern responseLinePatter = Pattern 080 .compile("^(" + HTTP_VERSION + ")" + SP + "([1-9][0-9][0-9])" 081 + SP + "(.*)$"); 082 083 private final Result.Factory resultFactory = new Result.Factory(this); 084 private boolean reportHeaderReceived = false; 085 private String switchingTo; 086 private UpgradeProvider protocolPlugin; 087 088 /* (non-Javadoc) 089 * @see org.jdrupes.httpcodec.protocols.http.HttpDecoder#resultFactory() 090 */ 091 @Override 092 protected Result.Factory resultFactory() { 093 return resultFactory; 094 } 095 096 /* (non-Javadoc) 097 * @see org.jdrupes.httpcodec.Decoder#decoding() 098 */ 099 @Override 100 public Class<HttpResponse> decoding() { 101 return HttpResponse.class; 102 } 103 104 /* (non-Javadoc) 105 * @see org.jdrupes.httpcodec.internal.Decoder#decode 106 */ 107 @Override 108 public Result decode(ByteBuffer in, Buffer out, boolean endOfInput) 109 throws ProtocolException { 110 return (Result)super.decode(in, out, endOfInput); 111 } 112 113 /** 114 * Checks whether the first line of a message is a valid response. 115 * If so, create a new response message object with basic information, else 116 * throw an exception. 117 * <P> 118 * Called by the base class when a first line is received. 119 * 120 * @param startLine the first line 121 * @throws HttpProtocolException if the line is not a correct request line 122 */ 123 @Override 124 protected HttpResponse newMessage(String startLine) 125 throws HttpProtocolException { 126 Matcher responseMatcher = responseLinePatter.matcher(startLine); 127 if (!responseMatcher.matches()) { 128 throw new HttpProtocolException(protocolVersion, 129 HttpStatus.BAD_REQUEST.statusCode(), 130 "Illegal request line"); 131 } 132 String httpVersion = responseMatcher.group(1); 133 int statusCode = Integer.parseInt(responseMatcher.group(2)); 134 String reasonPhrase = responseMatcher.group(3); 135 boolean found = false; 136 for (HttpProtocol v : HttpProtocol.values()) { 137 if (v.toString().equals(httpVersion)) { 138 protocolVersion = v; 139 found = true; 140 } 141 } 142 if (!found) { 143 throw new HttpProtocolException(HttpProtocol.HTTP_1_1, 144 HttpStatus.HTTP_VERSION_NOT_SUPPORTED); 145 } 146 return new HttpResponse(protocolVersion, statusCode, reasonPhrase, 147 false); 148 } 149 150 /* 151 * (non-Javadoc) 152 * 153 * @see 154 * org.jdrupes.httpcodec.HttpDecoder#headerReceived(org.jdrupes.httpcodec. 155 * HttpMessage) 156 */ 157 @Override 158 protected BodyMode headerReceived(HttpResponse message) 159 throws ProtocolException { 160 reportHeaderReceived = true; 161 // Adjust Retry-After 162 HttpField<?> hdr = message.fields().get(HttpField.RETRY_AFTER); 163 if (hdr != null && String.class 164 .isAssignableFrom(hdr.value().getClass())) { 165 String value = (String)hdr.value(); 166 if (Character.isDigit(value.charAt(0))) { 167 Instant base = message.findField( 168 HttpField.DATE, Converters.DATE_TIME) 169 .map(HttpField<Instant>::value).orElse(Instant.now()); 170 message.setField(new HttpField<>(HttpField.RETRY_AFTER, 171 base.plusSeconds(Long.parseLong(value)), 172 Converters.DATE_TIME)); 173 } 174 } 175 // Prepare protocol switch 176 if (message.statusCode() 177 == HttpStatus.SWITCHING_PROTOCOLS.statusCode()) { 178 Optional<String> protocol = message.findField( 179 HttpField.UPGRADE, Converters.STRING_LIST) 180 .map(l -> l.value().get(0)); 181 if (!protocol.isPresent()) { 182 throw new ProtocolException( 183 "Upgrade header field missing in response"); 184 } 185 switchingTo = protocol.get(); 186 // Load every time to support dynamic deployment of additional 187 // services in an OSGi environment. 188 protocolPlugin = StreamSupport.stream( 189 ServiceLoader.load(UpgradeProvider.class) 190 .spliterator(), false) 191 .filter(p -> p.supportsProtocol(protocol.get())) 192 .findFirst().orElseThrow(() -> new ProtocolException( 193 "Upgrade to protocol " + protocol.get() 194 + " not supported.")); 195 switchingTo = protocol.get(); 196 if (peerEncoder != null && peerEncoder.header().isPresent()) { 197 protocolPlugin.checkSwitchingResponse( 198 peerEncoder.header().get(), message); 199 } 200 } 201 // RFC 7230 3.3.3 (1. & 2.) 202 int statusCode = message.statusCode(); 203 HttpRequest request = Optional.ofNullable(peerEncoder) 204 .flatMap(Encoder::header).orElse(null); 205 if (request != null && request.method().equalsIgnoreCase("HEAD") 206 || (statusCode % 100) == 1 207 || statusCode == 204 208 || statusCode == 304 209 || (request != null 210 && request.method().equalsIgnoreCase("CONNECT") 211 && (statusCode % 100 == 2))) { 212 return BodyMode.NO_BODY; 213 } 214 Optional<HttpField<StringList>> transEncs = message.findField( 215 HttpField.TRANSFER_ENCODING, Converters.STRING_LIST); 216 // RFC 7230 3.3.3 (3.) 217 if (transEncs.isPresent()) { 218 StringList values = transEncs.get().value(); 219 if (values.get(values.size() - 1) 220 .equalsIgnoreCase(TransferCoding.CHUNKED.toString())) { 221 return BodyMode.CHUNKED; 222 } else { 223 return BodyMode.UNTIL_CLOSE; 224 } 225 } 226 // RFC 7230 3.3.3 (5.) 227 if (message.fields().containsKey(HttpField.CONTENT_LENGTH)) { 228 return BodyMode.LENGTH; 229 } 230 // RFC 7230 3.3.3 (7.) 231 return BodyMode.UNTIL_CLOSE; 232 } 233 234 /* (non-Javadoc) 235 * @see org.jdrupes.httpcodec.protocols.http.HttpDecoder#messageComplete 236 */ 237 @Override 238 protected Decoder.Result<HttpRequest> messageComplete( 239 Decoder.Result<HttpRequest> result) { 240 if (switchingTo != null) { 241 return resultFactory().newResult(false, false, 242 result.closeConnection(), result.isHeaderCompleted(), 243 switchingTo, 244 protocolPlugin.createRequestEncoder(switchingTo), 245 protocolPlugin.createResponseDecoder(switchingTo)); 246 } 247 return super.messageComplete(result); 248 } 249 250 /** 251 * The result from encoding a response. In addition to the usual 252 * codec result, a result decoder may signal to the invoker that the 253 * connection to the responder must be closed. 254 * 255 * The class is declared abstract to promote the usage of the factory 256 * method. 257 */ 258 public abstract static class Result extends HttpDecoder.Result<HttpRequest> 259 implements Codec.ProtocolSwitchResult { 260 261 private String newProtocol; 262 private Decoder<?, ?> newDecoder; 263 private Encoder<?, ?> newEncoder; 264 265 /** 266 * Returns a new result. 267 * 268 * @param overflow 269 * {@code true} if the data didn't fit in the out buffer 270 * @param underflow 271 * {@code true} if more data is expected 272 * @param closeConnection 273 * {@code true} if the connection should be closed 274 * @param headerCompleted 275 * indicates that the message header has been completed and 276 * the message (without body) is available 277 * @param newProtocol the name of the new protocol if a switch occurred 278 * @param newEncoder the new decoder if a switch occurred 279 * @param newDecoder the new decoder if a switch occurred 280 */ 281 protected Result(boolean overflow, boolean underflow, 282 boolean closeConnection, boolean headerCompleted, 283 String newProtocol, Encoder<?, ?> newEncoder, 284 Decoder<?, ?> newDecoder) { 285 super(overflow, underflow, closeConnection, headerCompleted, 286 null, false); 287 this.newProtocol = newProtocol; 288 this.newDecoder = newDecoder; 289 this.newEncoder = newEncoder; 290 } 291 292 @Override 293 public String newProtocol() { 294 return newProtocol; 295 } 296 297 @Override 298 public Decoder<?, ?> newDecoder() { 299 return newDecoder; 300 } 301 302 @Override 303 public Encoder<?, ?> newEncoder() { 304 return newEncoder; 305 } 306 307 /* (non-Javadoc) 308 * @see java.lang.Object#hashCode() 309 */ 310 @Override 311 public int hashCode() { 312 final int prime = 31; 313 int result = super.hashCode(); 314 result = prime * result 315 + ((newDecoder == null) ? 0 : newDecoder.hashCode()); 316 result = prime * result 317 + ((newEncoder == null) ? 0 : newEncoder.hashCode()); 318 result = prime * result 319 + ((newProtocol == null) ? 0 : newProtocol.hashCode()); 320 return result; 321 } 322 323 /* (non-Javadoc) 324 * @see java.lang.Object#equals(java.lang.Object) 325 */ 326 @Override 327 public boolean equals(Object obj) { 328 if (this == obj) { 329 return true; 330 } 331 if (!super.equals(obj)) { 332 return false; 333 } 334 if (!(obj instanceof Result)) { 335 return false; 336 } 337 Result other = (Result) obj; 338 if (newDecoder == null) { 339 if (other.newDecoder != null) { 340 return false; 341 } 342 } else if (!newDecoder.equals(other.newDecoder)) { 343 return false; 344 } 345 if (newEncoder == null) { 346 if (other.newEncoder != null) { 347 return false; 348 } 349 } else if (!newEncoder.equals(other.newEncoder)) { 350 return false; 351 } 352 if (newProtocol == null) { 353 if (other.newProtocol != null) { 354 return false; 355 } 356 } else if (!newProtocol.equals(other.newProtocol)) { 357 return false; 358 } 359 return true; 360 } 361 362 /* (non-Javadoc) 363 * @see java.lang.Object#toString() 364 */ 365 @Override 366 public String toString() { 367 StringBuilder builder = new StringBuilder(); 368 builder.append("HttpResponseDecoder.Result [overflow="); 369 builder.append(isOverflow()); 370 builder.append(", underflow="); 371 builder.append(isUnderflow()); 372 builder.append(", closeConnection="); 373 builder.append(closeConnection()); 374 builder.append(", headerCompleted="); 375 builder.append(isHeaderCompleted()); 376 builder.append(", "); 377 if (response() != null) { 378 builder.append("response="); 379 builder.append(response()); 380 builder.append(", "); 381 } 382 builder.append("responseOnly="); 383 builder.append(isResponseOnly()); 384 builder.append(", "); 385 if (newProtocol != null) { 386 builder.append("newProtocol="); 387 builder.append(newProtocol); 388 builder.append(", "); 389 } 390 if (newDecoder != null) { 391 builder.append("newDecoder="); 392 builder.append(newDecoder); 393 builder.append(", "); 394 } 395 if (newEncoder != null) { 396 builder.append("newEncoder="); 397 builder.append(newEncoder); 398 } 399 builder.append("]"); 400 return builder.toString(); 401 } 402 403 /** 404 * The Factory for (extended) results. 405 */ 406 protected static class Factory 407 extends HttpDecoder.Result.Factory<HttpRequest> { 408 409 private HttpResponseDecoder decoder; 410 411 /** 412 * Creates a new factory for the given decoder. 413 * 414 * @param decoder the decoder 415 */ 416 protected Factory(HttpResponseDecoder decoder) { 417 super(); 418 this.decoder = decoder; 419 } 420 421 public Result newResult(boolean overflow, boolean underflow, 422 boolean closeConnection, boolean headerCompleted, 423 String newProtocol, Encoder<?, ?> newEncoder, 424 Decoder<?, ?> newDecoder) { 425 return new Result(overflow, underflow, closeConnection, 426 headerCompleted, newProtocol, newEncoder, newDecoder) { 427 }; 428 } 429 430 /** 431 * Create a new (preliminary) result. This is invoked by the 432 * base class. We cannot supply the missing information yet. 433 * If necessary the result will be modified in 434 * {@link HttpResponseDecoder#decode(ByteBuffer, Buffer, boolean)}. 435 **/ 436 @Override 437 protected Result newResult( 438 boolean overflow, boolean underflow) { 439 Result result = newResult(overflow, underflow, decoder.isClosed(), 440 decoder.reportHeaderReceived, null, null, null); 441 decoder.reportHeaderReceived = false; 442 return result; 443 } 444 } 445 } 446 447}