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.io.IOException;
022import java.io.Writer;
023import java.net.URI;
024import java.nio.Buffer;
025import java.util.ServiceLoader;
026import java.util.stream.StreamSupport;
027
028import org.jdrupes.httpcodec.plugin.UpgradeProvider;
029import org.jdrupes.httpcodec.protocols.http.HttpConstants.HttpProtocol;
030import org.jdrupes.httpcodec.protocols.http.HttpEncoder;
031import org.jdrupes.httpcodec.protocols.http.HttpField;
032import org.jdrupes.httpcodec.protocols.http.HttpRequest;
033import org.jdrupes.httpcodec.protocols.http.HttpResponse;
034import org.jdrupes.httpcodec.types.Converters;
035import org.jdrupes.httpcodec.types.StringList;
036
037/**
038 * An encoder for HTTP requests that accepts a header and optional
039 * payload data end encodes it into a sequence of
040 * {@link Buffer}s.
041 * 
042 * ![HttpRequestEncoder](httprequestencoder.svg)
043 * 
044 * @startuml httprequestencoder.svg
045 * class HttpRequestEncoder {
046 *      +HttpRequestEncoder(Engine engine)
047 * }
048 * 
049 * class HttpEncoder<T extends HttpMessageHeader> {
050 * }
051 * 
052 * HttpEncoder <|-- HttpRequestEncoder : <<bind>> <T -> HttpRequest>
053 *
054 */
055public class HttpRequestEncoder 
056        extends HttpEncoder<HttpRequest, HttpResponse> {
057
058        private static Result.Factory resultFactory = new Result.Factory() {
059        };
060        
061        /* (non-Javadoc)
062         * @see org.jdrupes.httpcodec.Encoder#encoding()
063         */
064        @Override
065        public Class<HttpRequest> encoding() {
066                return HttpRequest.class;
067        }
068
069        /* (non-Javadoc)
070         * @see org.jdrupes.httpcodec.protocols.http.HttpEncoder#resultFactory()
071         */
072        @Override
073        protected Result.Factory resultFactory() {
074                return resultFactory;
075        }
076
077        /* (non-Javadoc)
078         * @see HttpEncoder#encode(HttpMessageHeader)
079         */
080        @Override
081        public void encode(HttpRequest messageHeader) {
082                if (messageHeader.protocol().equals(HttpProtocol.HTTP_1_1)) {
083                        // Make sure we have a Host field, RFC 7230 5.4
084                        if (!messageHeader.findStringValue(HttpField.HOST).isPresent()) {
085                                URI reqUri = messageHeader.requestUri();
086                                messageHeader.setField(HttpField.HOST, reqUri.getHost()
087                                                + (reqUri.getPort() < 0 ? "" 
088                                                                : (":" + reqUri.getPort())));
089                        }
090                }
091                messageHeader.findField(HttpField.UPGRADE, Converters.STRING_LIST)
092                        .ifPresent(field -> prepareUpgrade(field, messageHeader));
093                super.encode(messageHeader);
094        }
095
096        private void prepareUpgrade(
097                        HttpField<StringList> field, HttpRequest request) {
098                if (field.value().isEmpty()) {
099                        throw new IllegalArgumentException(
100                                        "Upgrade header field must have a value.");
101                }
102                String protocol = field.value().get(0);
103                // Load every time to support dynamic deployment of additional
104                // services in an OSGi environment.
105                UpgradeProvider protocolPlugin = StreamSupport.stream(
106                                ServiceLoader.load(UpgradeProvider.class).spliterator(), false)
107                                .filter(p -> p.supportsProtocol(protocol))
108                                .findFirst().get();
109                if (protocolPlugin == null) {
110                        // Not supported, maybe transparent to HTTP 
111                        return;
112                }
113                protocolPlugin.augmentInitialRequest(request);
114        }
115        
116        /* (non-Javadoc)
117         * @see Encoder#startMessage(MessageHeader, java.io.Writer)
118         */
119        @Override
120        protected void startMessage(HttpRequest messageHeader, Writer writer)
121                throws IOException {
122                writer.write(messageHeader.method());
123                writer.write(" ");
124                writer.write(messageHeader.requestUri().toString());
125                writer.write(" ");
126                writer.write(messageHeader.protocol().toString());
127                writer.write("\r\n");
128        }
129        
130        /**
131         * Results from {@link HttpRequestEncoder} add no additional
132         * information to 
133         * {@link org.jdrupes.httpcodec.protocols.http.HttpEncoder.Result}. This
134         * class just provides a factory for creating concrete results.
135         */
136        public static class Result extends HttpEncoder.Result {
137
138                protected Result(boolean overflow, boolean underflow,
139                        boolean closeConnection) {
140                        super(overflow, underflow, closeConnection);
141                }
142        
143                /**
144                 * A concrete factory for creating new Results.
145                 */
146                protected static class Factory extends HttpEncoder.Result.Factory {
147                }               
148        }
149}