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}