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