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.server;
020
021import java.io.IOException;
022import java.io.Writer;
023import java.nio.Buffer;
024import java.nio.ByteBuffer;
025import java.time.Instant;
026import java.util.Optional;
027import java.util.ServiceLoader;
028import java.util.stream.StreamSupport;
029import org.jdrupes.httpcodec.Codec;
030import org.jdrupes.httpcodec.Decoder;
031import org.jdrupes.httpcodec.Encoder;
032import org.jdrupes.httpcodec.plugin.UpgradeProvider;
033import static org.jdrupes.httpcodec.protocols.http.HttpConstants.*;
034import org.jdrupes.httpcodec.protocols.http.HttpConstants.HttpProtocol;
035import org.jdrupes.httpcodec.protocols.http.HttpEncoder;
036import org.jdrupes.httpcodec.protocols.http.HttpField;
037import org.jdrupes.httpcodec.protocols.http.HttpRequest;
038import org.jdrupes.httpcodec.protocols.http.HttpResponse;
039import org.jdrupes.httpcodec.types.CacheControlDirectives;
040import org.jdrupes.httpcodec.types.Converters;
041
042/**
043 * An encoder for HTTP responses. It accepts a header and optional
044 * payload data end encodes it into a sequence of
045 * {@link Buffer}s.
046 * 
047 * ![HttpResponseEncoder](httpresponseencoder.svg)
048 *
049 * Headers
050 * -------
051 * 
052 * ### Date ###
053 * 
054 * The encoder automatically adds a `Date` header as specified
055 * in [RFC 7231, Section 7.1.1.2](https://tools.ietf.org/html/rfc7231#section-7.1.1.2).
056 * Any existing `Date` header will be overwritten. 
057 * 
058 * ### Expires ###
059 * 
060 * If the protocol is HTTP 1.0 and the response includes a `Cache-Control`
061 * header field with a `max-age` directive, an `Expires` header field
062 * with the same information is generated (see
063 * [RFC 7234, Section 5.3](https://tools.ietf.org/html/rfc7234#section-5.3)).
064 * 
065 * @startuml httpresponseencoder.svg
066 * class HttpResponseEncoder {
067 *      +HttpResponseEncoder()
068 *      +void encode(HttpResponse messageHeader)
069 * +Result encode(Buffer in, ByteBuffer out, boolean endOfInput)
070 * }
071 * 
072 * class HttpEncoder<T extends HttpMessageHeader> {
073 * }
074 * 
075 * HttpEncoder <|-- HttpResponseEncoder : <<bind>> <T -> HttpResponse>
076 * 
077 * @enduml
078 */
079public class HttpResponseEncoder extends HttpEncoder<HttpResponse, HttpRequest>
080        implements Encoder<HttpResponse, HttpRequest> {
081
082        private static Result.Factory resultFactory = new Result.Factory() {
083        };
084        
085        private String switchingTo;
086        private UpgradeProvider protocolPlugin;
087
088        /* (non-Javadoc)
089         * @see org.jdrupes.httpcodec.protocols.http.HttpEncoder#resultFactory()
090         */
091        @Override
092        protected Result.Factory resultFactory() {
093                return resultFactory;
094        }
095
096        /* (non-Javadoc)
097         * @see org.jdrupes.httpcodec.Encoder#encoding()
098         */
099        @Override
100        public Class<HttpResponse> encoding() {
101                return HttpResponse.class;
102        }
103
104        /* (non-Javadoc)
105         * @see HttpEncoder#encode(HttpMessageHeader)
106         */
107        @Override
108        public void encode(HttpResponse messageHeader) {
109                if (messageHeader.statusCode()
110                                        == HttpStatus.SWITCHING_PROTOCOLS.statusCode()) {
111                        switchingTo = prepareSwitchProtocol(messageHeader);
112                }
113                
114                // Make sure we have an up-to-date Date, RFC 7231 7.1.1.2
115                messageHeader.setField(HttpField.DATE, Instant.now());
116
117                // ensure backward compatibility
118                if (messageHeader.protocol().compareTo(HttpProtocol.HTTP_1_1) < 0) {
119                        // Create Expires
120                        Optional<HttpField<CacheControlDirectives>> cacheCtrl 
121                            = messageHeader.findField(HttpField.CACHE_CONTROL, 
122                                            Converters.CACHE_CONTROL_LIST);
123                        if (cacheCtrl.isPresent() 
124                                        && !messageHeader.fields().containsKey(HttpField.EXPIRES)) {
125                                Optional<Long> maxAge = cacheCtrl.get().value().stream()
126                                                .filter(d -> "max-age".equalsIgnoreCase(d.name()))
127                                                .map(d -> d.value()).map(v -> Long.parseLong(v.get()))
128                                                .findFirst();
129                                if (maxAge.isPresent()) {
130                                        messageHeader.setField(HttpField.EXPIRES, 
131                                                        Instant.now().plusSeconds(maxAge.get()));
132                                }
133                        }
134                }
135
136                // Check the content length rules
137                checkContentLength(messageHeader);
138                
139                // Finally encode
140                super.encode(messageHeader);
141        }
142
143        /* (non-Javadoc)
144         * @see org.jdrupes.httpcodec.internal.Encoder#encode(java.nio.ByteBuffer, java.nio.ByteBuffer)
145         */
146        @Override
147        public Result encode(Buffer in, ByteBuffer out, boolean endOfInput) {
148                Result result = (Result)super.encode(in, out, endOfInput);
149                if (switchingTo != null && endOfInput 
150                                && !result.isUnderflow() && !result.isOverflow()) {
151                        // Last invocation of encode
152                        return resultFactory().newResult(false, false, 
153                                        result.closeConnection(), switchingTo, 
154                                        protocolPlugin.createRequestDecoder(switchingTo), 
155                                        protocolPlugin.createResponseEncoder(switchingTo));
156                }
157                return result;
158        }
159
160        private void checkContentLength(HttpResponse messageHeader) {
161                // RFC 7230 3.3.2 
162                boolean forbidden = messageHeader.fields()
163                                .containsKey(HttpField.TRANSFER_ENCODING)
164                                || messageHeader.statusCode() % 100 == 1
165                                || messageHeader.statusCode() 
166                                                == HttpStatus.NO_CONTENT.statusCode()
167                                || (messageHeader.request().map(
168                                                r -> r.method().equalsIgnoreCase("CONNECT"))
169                                                .orElse(false)
170                                        && messageHeader.statusCode() % 100 == 2);
171                if (messageHeader.fields().containsKey(HttpField.CONTENT_LENGTH)) {
172                        if (forbidden) {
173                                messageHeader.removeField(HttpField.CONTENT_LENGTH);
174                        }
175                        return;
176                }
177                // No content length header, maybe we should add one?
178                if (forbidden || messageHeader.hasPayload()) {
179                        // Not needed or data will determine header
180                        return;
181                }
182                // Don't add header if optional
183                if (messageHeader.request().map(
184                                r -> r.method().equalsIgnoreCase("HEAD"))
185                                .orElse(false)
186                        || messageHeader.statusCode() 
187                                        == HttpStatus.NOT_MODIFIED.statusCode()) {
188                        return;
189                }
190                // Add 0 content length
191                messageHeader.setField(new HttpField<>(
192                                HttpField.CONTENT_LENGTH, 0L, Converters.LONG));
193        }
194
195        private String prepareSwitchProtocol(HttpResponse response) {
196                Optional<String> protocol = response
197                                .findField(HttpField.UPGRADE, Converters.STRING_LIST)
198                                .map(l -> l.value().get(0));
199                if (!protocol.isPresent()) {
200                        response.setStatus(HttpStatus.BAD_REQUEST)
201                                .setHasPayload(false).clearHeaders();
202                        return null;
203                }
204                // Load every time to support dynamic deployment of additional
205                // services in an OSGi environment.
206                protocolPlugin = StreamSupport.stream(
207                                ServiceLoader.load(UpgradeProvider.class)
208                                .spliterator(), false)
209                                .filter(p -> p.supportsProtocol(protocol.get()))
210                                .findFirst().orElse(null);
211                if (protocolPlugin == null) {
212                        response.setStatus(HttpStatus.BAD_REQUEST)
213                                .setHasPayload(false).clearHeaders();
214                        return null;
215                }
216                protocolPlugin.augmentInitialResponse(response);
217                if (response.statusCode() 
218                                != HttpStatus.SWITCHING_PROTOCOLS.statusCode()) {
219                        // Not switching after all
220                        return null;
221                }
222                return protocol.get();
223        }
224
225        /* (non-Javadoc)
226         * @see org.jdrupes.httpcodec.internal.Encoder#startMessage(java.io.Writer)
227         */
228        @Override
229        protected void startMessage(HttpResponse response, Writer writer)
230                throws IOException {
231                writer.write(response.protocol().toString());
232                writer.write(" ");
233                writer.write(Integer.toString(response.statusCode()));
234                writer.write(" ");
235                writer.write(response.reasonPhrase());
236                writer.write("\r\n");
237        }
238
239    @Override
240    protected boolean forceCloseAfterBody() {
241        return messageHeader.protocol()
242                .compareTo(HttpProtocol.HTTP_1_0) <= 0;
243    }
244    
245        /**
246         * The result from encoding a response. In addition to the usual
247         * codec result, a response encoder may signal to the invoker that the
248         * connection to the requester must be closed and that the protocol has
249         * been switched.
250         */
251        public abstract static class Result extends HttpEncoder.Result
252                implements Codec.ProtocolSwitchResult {
253
254                private String newProtocol;
255                private Decoder<?, ?> newDecoder;
256                private Encoder<?, ?> newEncoder;
257                
258                /**
259                 * Returns a new result.
260                 * 
261                 * @param overflow
262                 *            {@code true} if the data didn't fit in the out buffer
263                 * @param underflow
264                 *            {@code true} if more data is expected
265                 * @param closeConnection
266                 *            {@code true} if the connection should be closed
267                 * @param newProtocol the name of the new protocol if a switch occurred
268                 * @param newDecoder the new decoder if a switch occurred
269                 * @param newEncoder the new decoder if a switch occurred
270                 */
271                protected Result(boolean overflow, boolean underflow,
272                        boolean closeConnection, String newProtocol,
273                        Decoder<?, ?> newDecoder, Encoder<?, ?> newEncoder) {
274                        super(overflow, underflow, closeConnection);
275                        this.newProtocol = newProtocol;
276                        this.newEncoder = newEncoder;
277                        this.newDecoder = newDecoder;
278                }
279
280                @Override
281                public String newProtocol() {
282                        return newProtocol;
283                }
284                
285                @Override
286                public Encoder<?, ?> newEncoder() {
287                        return newEncoder;
288                }
289                
290                @Override
291                public Decoder<?, ?> newDecoder() {
292                        return newDecoder;
293                }
294
295                /* (non-Javadoc)
296                 * @see java.lang.Object#hashCode()
297                 */
298                @Override
299                public int hashCode() {
300                        final int prime = 31;
301                        int result = super.hashCode();
302                        result = prime * result
303                                + ((newDecoder == null) ? 0 : newDecoder.hashCode());
304                        result = prime * result
305                                + ((newEncoder == null) ? 0 : newEncoder.hashCode());
306                        result = prime * result
307                                + ((newProtocol == null) ? 0 : newProtocol.hashCode());
308                        return result;
309                }
310
311                /* (non-Javadoc)
312                 * @see java.lang.Object#equals(java.lang.Object)
313                 */
314                @Override
315                public boolean equals(Object obj) {
316                        if (this == obj) {
317                                return true;
318                        }
319                        if (!super.equals(obj)) {
320                                return false;
321                        }
322                        if (!(obj instanceof Result)) {
323                                return false;
324                        }
325                        Result other = (Result) obj;
326                        if (newDecoder == null) {
327                                if (other.newDecoder != null) {
328                                        return false;
329                                }
330                        } else if (!newDecoder.equals(other.newDecoder)) {
331                                return false;
332                        }
333                        if (newEncoder == null) {
334                                if (other.newEncoder != null) {
335                                        return false;
336                                }
337                        } else if (!newEncoder.equals(other.newEncoder)) {
338                                return false;
339                        }
340                        if (newProtocol == null) {
341                                if (other.newProtocol != null) {
342                                        return false;
343                                }
344                        } else if (!newProtocol.equals(other.newProtocol)) {
345                                return false;
346                        }
347                        return true;
348                }
349
350                /* (non-Javadoc)
351                 * @see java.lang.Object#toString()
352                 */
353                @Override
354                public String toString() {
355                        StringBuilder builder = new StringBuilder();
356                        builder.append("HttpResponseEncoder.Result [overflow=");
357                        builder.append(isOverflow());
358                        builder.append(", underflow=");
359                        builder.append(isUnderflow());
360                        builder.append(", closeConnection=");
361                        builder.append(closeConnection());
362                        builder.append(", ");
363                        if (newProtocol != null) {
364                                builder.append("newProtocol=");
365                                builder.append(newProtocol);
366                                builder.append(", ");
367                        }
368                        if (newDecoder != null) {
369                                builder.append("newDecoder=");
370                                builder.append(newDecoder);
371                                builder.append(", ");
372                        }
373                        if (newEncoder != null) {
374                                builder.append("newEncoder=");
375                                builder.append(newEncoder);
376                        }
377                        builder.append("]");
378                        return builder.toString();
379                }
380
381                /**
382                 * A factory for creating new Results.
383                 */
384                protected static class Factory extends HttpEncoder.Result.Factory {
385                        
386                        /**
387                         * Create a new result.
388                         * 
389                         * @param overflow
390                         *            {@code true} if the data didn't fit in the out buffer
391                         * @param underflow
392                         *            {@code true} if more data is expected
393                         * @param closeConnection
394                         *            {@code true} if the connection should be closed
395                         * @param newProtocol the name of the new protocol if a switch occurred
396                         * @param newDecoder the new decoder if a switch occurred
397                         * @param newEncoder the new decoder if a switch occurred
398                         * @return the result
399                         */
400                        public Result newResult(boolean overflow, boolean underflow,
401                                boolean closeConnection, String newProtocol,
402                                Decoder<?, ?> newDecoder, Encoder<?, ?> newEncoder) {
403                                return new Result(overflow, underflow, closeConnection,
404                                                newProtocol, newDecoder, newEncoder) {
405                                };
406                        }
407
408                        /**
409                         * Create a new (preliminary) result. This is invoked by the
410                         * base class. We cannot supply the missing information yet.
411                         * If necessary the result will be modified in 
412                         * {@link HttpResponseEncoder#encode(Buffer, ByteBuffer, boolean)}.
413                         * 
414                         * @param overflow
415                         *            {@code true} if the data didn't fit in the out buffer
416                         * @param underflow
417                         *            {@code true} if more data is expected
418                         * @param closeConnection
419                         *            {@code true} if the connection should be closed
420                         * @return the result
421                         */
422                        @Override
423                        public Result newResult(boolean overflow, boolean underflow,
424                                boolean closeConnection) {
425                                return newResult(overflow, underflow, closeConnection,
426                                                null, null, null);
427                        }
428                        
429                }               
430
431        }
432}