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.net.URISyntaxException; 025import java.nio.Buffer; 026import java.util.Objects; 027import java.util.Optional; 028import java.util.ServiceLoader; 029import java.util.stream.StreamSupport; 030 031import org.jdrupes.httpcodec.plugin.UpgradeProvider; 032import org.jdrupes.httpcodec.protocols.http.HttpConstants.HttpProtocol; 033import org.jdrupes.httpcodec.protocols.http.HttpEncoder; 034import org.jdrupes.httpcodec.protocols.http.HttpField; 035import org.jdrupes.httpcodec.protocols.http.HttpRequest; 036import org.jdrupes.httpcodec.protocols.http.HttpResponse; 037import org.jdrupes.httpcodec.types.Converters; 038import org.jdrupes.httpcodec.types.StringList; 039 040/** 041 * An encoder for HTTP requests. It accepts a header and optional 042 * payload data and encodes it into a sequence of {@link Buffer}s. 043 * 044 * ![HttpRequestEncoder](httprequestencoder.svg) 045 * 046 * @startuml httprequestencoder.svg 047 * class HttpRequestEncoder { 048 * +HttpRequestEncoder(Engine engine) 049 * } 050 * 051 * class HttpEncoder<T extends HttpMessageHeader> { 052 * } 053 * 054 * HttpEncoder <|-- HttpRequestEncoder : <<bind>> <T -> HttpRequest> 055 * 056 */ 057public class HttpRequestEncoder 058 extends HttpEncoder<HttpRequest, HttpResponse> { 059 060 private static Result.Factory resultFactory = new Result.Factory() { 061 }; 062 063 /* 064 * (non-Javadoc) 065 * 066 * @see org.jdrupes.httpcodec.Encoder#encoding() 067 */ 068 @Override 069 public Class<HttpRequest> encoding() { 070 return HttpRequest.class; 071 } 072 073 /* 074 * (non-Javadoc) 075 * 076 * @see org.jdrupes.httpcodec.protocols.http.HttpEncoder#resultFactory() 077 */ 078 @Override 079 protected Result.Factory resultFactory() { 080 return resultFactory; 081 } 082 083 /* 084 * (non-Javadoc) 085 * 086 * @see HttpEncoder#encode(HttpMessageHeader) 087 */ 088 @Override 089 public void encode(HttpRequest messageHeader) { 090 if (messageHeader.protocol().equals(HttpProtocol.HTTP_1_1)) { 091 // Make sure we have a Host field, RFC 7230 5.4 092 if (!messageHeader.findStringValue(HttpField.HOST).isPresent()) { 093 messageHeader.setField(HttpField.HOST, messageHeader.host() 094 + (messageHeader.port() < 0 ? "" 095 : (":" + messageHeader.port()))); 096 } 097 } 098 messageHeader.findField(HttpField.UPGRADE, Converters.STRING_LIST) 099 .ifPresent(field -> prepareUpgrade(field, messageHeader)); 100 super.encode(messageHeader); 101 } 102 103 private void prepareUpgrade( 104 HttpField<StringList> field, HttpRequest request) { 105 if (field.value().isEmpty()) { 106 throw new IllegalArgumentException( 107 "Upgrade header field must have a value."); 108 } 109 String protocol = field.value().get(0); 110 // Load every time to support dynamic deployment of additional 111 // services in an OSGi environment. 112 Optional<UpgradeProvider> protocolPlugin = StreamSupport.stream( 113 ServiceLoader.load(UpgradeProvider.class).spliterator(), false) 114 .filter(p -> p.supportsProtocol(protocol)) 115 .findFirst(); 116 if (!protocolPlugin.isPresent()) { 117 // Not supported, maybe transparent to HTTP 118 return; 119 } 120 protocolPlugin.get().augmentInitialRequest(request); 121 } 122 123 /** 124 * Writes the 125 * [request line](https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.1). 126 */ 127 @Override 128 protected void startMessage(HttpRequest messageHeader, Writer writer) 129 throws IOException { 130 writer.write(messageHeader.method()); 131 writer.write(" "); 132 URI req = messageHeader.requestUri(); 133 Optional<String> hostHdr 134 = messageHeader.findStringValue(HttpField.HOST); 135 // https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1 136 if (hostHdr.isPresent() && Objects.equals(hostHdr.get(), 137 Optional.ofNullable(req.getHost()).orElse("") 138 + ((req.getPort() < 0) ? "" : (":" + req.getPort()))) 139 && req.getUserInfo() == null) { 140 try { 141 req = new URI(null, null, null, -1, 142 req.getPath().isEmpty() ? "/" : req.getPath(), 143 req.getQuery(), null); 144 } catch (URISyntaxException e) { 145 // Shouldn't happen, well in case it does, use original. 146 } 147 } else if (req.getFragment() != null) { 148 // https://datatracker.ietf.org/doc/html/rfc7230#section-5.1 149 try { 150 req = new URI(req.getScheme(), req.getUserInfo(), 151 req.getHost(), req.getPort(), req.getPath(), 152 req.getQuery(), null); 153 } catch (URISyntaxException e) { 154 // Shouldn't happen, well in case it does, use original. 155 } 156 } 157 writer.write(req.toString()); 158 writer.write(" "); 159 writer.write(messageHeader.protocol().toString()); 160 writer.write("\r\n"); 161 } 162 163 /** 164 * Results from {@link HttpRequestEncoder} add no additional 165 * information to 166 * {@link org.jdrupes.httpcodec.protocols.http.HttpEncoder.Result}. This 167 * class just provides a factory for creating concrete results. 168 */ 169 public static class Result extends HttpEncoder.Result { 170 171 protected Result(boolean overflow, boolean underflow, 172 boolean closeConnection) { 173 super(overflow, underflow, closeConnection); 174 } 175 176 /** 177 * A concrete factory for creating new Results. 178 */ 179 protected static class Factory extends HttpEncoder.Result.Factory { 180 } 181 } 182}