1/*
2 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.incubator.http;
27
28import jdk.incubator.http.internal.common.HttpHeadersImpl;
29import jdk.incubator.http.internal.websocket.WebSocketRequest;
30
31import java.io.IOException;
32import java.net.InetSocketAddress;
33import java.net.ProxySelector;
34import java.net.URI;
35import java.security.AccessControlContext;
36import java.time.Duration;
37import java.util.Locale;
38import java.util.Optional;
39
40import static jdk.incubator.http.internal.common.Utils.ALLOWED_HEADERS;
41
42class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
43
44    private final HttpHeaders userHeaders;
45    private final HttpHeadersImpl systemHeaders;
46    private final URI uri;
47    private InetSocketAddress authority; // only used when URI not specified
48    private final String method;
49    final BodyProcessor requestProcessor;
50    final boolean secure;
51    final boolean expectContinue;
52    private boolean isWebSocket;
53    private AccessControlContext acc;
54    private final Duration duration;
55    private final Optional<HttpClient.Version> version;
56
57    /**
58     * Creates an HttpRequestImpl from the given builder.
59     */
60    public HttpRequestImpl(HttpRequestBuilderImpl builder) {
61        this.method = builder.method();
62        this.userHeaders = ImmutableHeaders.of(builder.headers().map(), ALLOWED_HEADERS);
63        this.systemHeaders = new HttpHeadersImpl();
64        this.uri = builder.uri();
65        this.expectContinue = builder.expectContinue();
66        this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
67        if (builder.body() == null) {
68            this.requestProcessor = HttpRequest.noBody();
69        } else {
70            this.requestProcessor = builder.body();
71        }
72        this.duration = builder.duration();
73        this.version = builder.version();
74    }
75
76    /**
77     * Creates an HttpRequestImpl from the given request.
78     */
79    public HttpRequestImpl(HttpRequest request) {
80        this.method = request.method();
81        this.userHeaders = request.headers();
82        if (request instanceof HttpRequestImpl) {
83            this.systemHeaders = ((HttpRequestImpl) request).systemHeaders;
84            this.isWebSocket = ((HttpRequestImpl) request).isWebSocket;
85        } else {
86            this.systemHeaders = new HttpHeadersImpl();
87        }
88        this.uri = request.uri();
89        this.expectContinue = request.expectContinue();
90        this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
91        if (!request.bodyProcessor().isPresent()) {
92            this.requestProcessor = HttpRequest.noBody();
93        } else {
94            this.requestProcessor = request.bodyProcessor().get();
95        }
96        this.duration = request.duration();
97        this.version = request.version();
98    }
99
100    /** Creates a HttpRequestImpl using fields of an existing request impl. */
101    public HttpRequestImpl(URI uri,
102                           String method,
103                           HttpRequestImpl other) {
104        this.method = method == null? "GET" : method;
105        this.userHeaders = other.userHeaders;
106        this.isWebSocket = other.isWebSocket;
107        this.systemHeaders = other.systemHeaders;
108        this.uri = uri;
109        this.expectContinue = other.expectContinue;
110        this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
111        this.requestProcessor = other.requestProcessor;
112        this.acc = other.acc;
113        this.duration = other.duration;
114        this.version = other.version();
115    }
116
117    /* used for creating CONNECT requests  */
118    HttpRequestImpl(String method, HttpClientImpl client,
119                    InetSocketAddress authority) {
120        // TODO: isWebSocket flag is not specified, but the assumption is that
121        // such a request will never be made on a connection that will be returned
122        // to the connection pool (we might need to revisit this constructor later)
123        this.method = method;
124        this.systemHeaders = new HttpHeadersImpl();
125        this.userHeaders = ImmutableHeaders.empty();
126        this.uri = URI.create("socket://" + authority.getHostString() + ":" + Integer.toString(authority.getPort()) + "/");
127        this.requestProcessor = HttpRequest.noBody();
128        this.authority = authority;
129        this.secure = false;
130        this.expectContinue = false;
131        this.duration = null;
132        this.version = Optional.of(client.version());
133    }
134
135    /**
136     * Creates a HttpRequestImpl from the given set of Headers and the associated
137     * "parent" request. Fields not taken from the headers are taken from the
138     * parent.
139     */
140    static HttpRequestImpl createPushRequest(HttpRequestImpl parent,
141                                             HttpHeadersImpl headers)
142        throws IOException
143    {
144        return new HttpRequestImpl(parent, headers);
145    }
146
147    // only used for push requests
148    private HttpRequestImpl(HttpRequestImpl parent, HttpHeadersImpl headers)
149        throws IOException
150    {
151        this.method = headers.firstValue(":method")
152                .orElseThrow(() -> new IOException("No method in Push Promise"));
153        String path = headers.firstValue(":path")
154                .orElseThrow(() -> new IOException("No path in Push Promise"));
155        String scheme = headers.firstValue(":scheme")
156                .orElseThrow(() -> new IOException("No scheme in Push Promise"));
157        String authority = headers.firstValue(":authority")
158                .orElseThrow(() -> new IOException("No authority in Push Promise"));
159        StringBuilder sb = new StringBuilder();
160        sb.append(scheme).append("://").append(authority).append(path);
161        this.uri = URI.create(sb.toString());
162
163        this.userHeaders = ImmutableHeaders.of(headers.map(), ALLOWED_HEADERS);
164        this.systemHeaders = parent.systemHeaders;
165        this.expectContinue = parent.expectContinue;
166        this.secure = parent.secure;
167        this.requestProcessor = parent.requestProcessor;
168        this.acc = parent.acc;
169        this.duration = parent.duration;
170        this.version = parent.version;
171    }
172
173    @Override
174    public String toString() {
175        return (uri == null ? "" : uri.toString()) + " " + method;
176    }
177
178    @Override
179    public HttpHeaders headers() {
180        return userHeaders;
181    }
182
183    InetSocketAddress authority() { return authority; }
184
185    void setH2Upgrade(Http2ClientImpl h2client) {
186        systemHeaders.setHeader("Connection", "Upgrade, HTTP2-Settings");
187        systemHeaders.setHeader("Upgrade", "h2c");
188        systemHeaders.setHeader("HTTP2-Settings", h2client.getSettingsString());
189    }
190
191    @Override
192    public boolean expectContinue() { return expectContinue; }
193
194    InetSocketAddress proxy(HttpClientImpl client) {
195        ProxySelector ps = client.proxy().orElse(null);
196        if (ps == null) {
197            ps = client.proxy().orElse(null);
198        }
199        if (ps == null || method.equalsIgnoreCase("CONNECT")) {
200            return null;
201        }
202        return (InetSocketAddress)ps.select(uri).get(0).address();
203    }
204
205    boolean secure() { return secure; }
206
207    @Override
208    public void isWebSocket(boolean is) {
209        isWebSocket = is;
210    }
211
212    boolean isWebSocket() {
213        return isWebSocket;
214    }
215
216//    /** Returns the follow-redirects setting for this request. */
217//    @Override
218//    public jdk.incubator.http.HttpClient.Redirect followRedirects() {
219//        return followRedirects;
220//    }
221
222    @Override
223    public Optional<BodyProcessor> bodyProcessor() {
224        return Optional.of(requestProcessor);
225    }
226
227    /**
228     * Returns the request method for this request. If not set explicitly,
229     * the default method for any request is "GET".
230     */
231    @Override
232    public String method() { return method; }
233
234    @Override
235    public URI uri() { return uri; }
236
237    @Override
238    public Duration duration() {
239        return duration;
240    }
241
242//    HttpClientImpl client() {
243//        return client;
244//    }
245
246    HttpHeaders getUserHeaders() { return userHeaders; }
247
248    HttpHeadersImpl getSystemHeaders() { return systemHeaders; }
249
250    @Override
251    public Optional<HttpClient.Version> version() { return version; }
252
253    void addSystemHeader(String name, String value) {
254        systemHeaders.addHeader(name, value);
255    }
256
257    @Override
258    public void setSystemHeader(String name, String value) {
259        systemHeaders.setHeader(name, value);
260    }
261
262//    @Override
263//    public <T> HttpResponse<T>
264//    response(HttpResponse.BodyHandler<T> responseHandler)
265//        throws IOException, InterruptedException
266//    {
267//        if (!sent.compareAndSet(false, true)) {
268//            throw new IllegalStateException("request already sent");
269//        }
270//        MultiExchange<Void,T> mex = new MultiExchange<>(this, responseHandler);
271//        return mex.response();
272//    }
273//
274//    @Override
275//    public <T> CompletableFuture<HttpResponse<T>>
276//    responseAsync(HttpResponse.BodyHandler<T> responseHandler)
277//    {
278//        if (!sent.compareAndSet(false, true)) {
279//            throw new IllegalStateException("request already sent");
280//        }
281//        MultiExchange<Void,T> mex = new MultiExchange<>(this, responseHandler);
282//        return mex.responseAsync(null)
283//                  .thenApply((HttpResponseImpl<T> b) -> (HttpResponse<T>) b);
284//    }
285//
286//    @Override
287//    public <U, T> CompletableFuture<U>
288//    multiResponseAsync(HttpResponse.MultiProcessor<U, T> responseHandler)
289//    {
290//        if (!sent.compareAndSet(false, true)) {
291//            throw new IllegalStateException("request already sent");
292//        }
293//        MultiExchange<U,T> mex = new MultiExchange<>(this, responseHandler);
294//        return mex.multiResponseAsync();
295//    }
296
297    public InetSocketAddress getAddress(HttpClientImpl client) {
298        URI uri = uri();
299        if (uri == null) {
300            return authority();
301        }
302        int port = uri.getPort();
303        if (port == -1) {
304            if (uri.getScheme().equalsIgnoreCase("https")) {
305                port = 443;
306            } else {
307                port = 80;
308            }
309        }
310        String host = uri.getHost();
311        if (proxy(client) == null) {
312            return new InetSocketAddress(host, port);
313        } else {
314            return InetSocketAddress.createUnresolved(host, port);
315        }
316    }
317}
318