All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.client.transport.HttpRequest Maven / Gradle / Ivy

There is a newer version: 2.0.32
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.client.transport;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.eclipse.jetty.client.CompletableResponseListener;
import org.eclipse.jetty.client.Connection;
import org.eclipse.jetty.client.ContentResponse;
import org.eclipse.jetty.client.Destination;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.PathRequestContent;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.client.RequestListeners;
import org.eclipse.jetty.client.Response;
import org.eclipse.jetty.client.internal.NotifyingRequestListeners;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.Transport;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.URIUtil;

public class HttpRequest implements Request
{
    private static final URI NULL_URI = URI.create("null:0");

    private final HttpFields.Mutable headers = HttpFields.build();
    private final Fields params = new Fields(true);
    private final ResponseListeners responseListeners = new ResponseListeners();
    private final AtomicReference aborted = new AtomicReference<>();
    private final HttpClient client;
    private final HttpConversation conversation;
    private Connection connection;
    private String scheme;
    private String host;
    private int port;
    private String path;
    private String query;
    private URI uri;
    private Transport transport;
    private String method = HttpMethod.GET.asString();
    private HttpVersion version = HttpVersion.HTTP_1_1;
    private boolean versionExplicit;
    private long idleTimeout = -1;
    private long timeout;
    private long timeoutNanoTime = Long.MAX_VALUE;
    private Content content;
    private boolean followRedirects;
    private List cookies;
    private Map attributes;
    private NotifyingRequestListeners requestListeners;
    private BiFunction pushHandler;
    private Supplier trailers;
    private Object tag;
    private boolean normalized;

    public HttpRequest(HttpClient client, HttpConversation conversation, URI uri)
    {
        // URIs built from strings that have an internationalized domain name (IDN)
        // are parsed without errors, but uri.getHost() returns null.
        if (uri.getHost() == null)
            throw new IllegalArgumentException(String.format("Invalid URI host: null (authority: %s)", uri.getRawAuthority()));
        this.client = client;
        this.conversation = conversation;
        scheme = uri.getScheme();
        host = uri.getHost();
        port = HttpClient.normalizePort(scheme, uri.getPort());
        path = uri.getRawPath();
        query = uri.getRawQuery();
        extractParams(query);

        followRedirects(client.isFollowRedirects());
        HttpField acceptEncodingField = client.getContentDecoderFactories().getAcceptEncodingField();
        if (acceptEncodingField != null)
            headers.put(acceptEncodingField);
        HttpField userAgentField = client.getUserAgentField();
        if (userAgentField != null)
            headers.put(userAgentField);
    }

    public HttpRequest copy(URI newURI)
    {
        if (newURI == null)
        {
            newURI = HttpURI.from(getScheme(), getHost(), getPort(), null).toURI();
        }

        HttpRequest newRequest = copyInstance(newURI);
        newRequest.method(getMethod())
            .version(getVersion())
            .body(getBody())
            .idleTimeout(getIdleTimeout(), TimeUnit.MILLISECONDS)
            .timeout(getTimeout(), TimeUnit.MILLISECONDS)
            .followRedirects(isFollowRedirects())
            .tag(getTag())
            .headers(h -> h.clear().add(getHeaders())
                // Remove the headers that depend on the URI.
                .remove(EnumSet.of(
                    HttpHeader.HOST,
                    HttpHeader.EXPECT,
                    HttpHeader.COOKIE,
                    HttpHeader.AUTHORIZATION,
                    HttpHeader.PROXY_AUTHORIZATION
                ))
            );

        return newRequest;
    }

    protected HttpRequest copyInstance(URI newURI)
    {
        return new HttpRequest(getHttpClient(), getConversation(), newURI);
    }

    protected HttpClient getHttpClient()
    {
        return client;
    }

    public HttpConversation getConversation()
    {
        return conversation;
    }

    @Override
    public Connection getConnection()
    {
        return connection;
    }

    void setConnection(Connection connection)
    {
        this.connection = connection;
    }

    @Override
    public String getScheme()
    {
        return scheme;
    }

    @Override
    public Request scheme(String scheme)
    {
        this.scheme = URIUtil.normalizeScheme(scheme);
        this.uri = null;
        return this;
    }

    @Override
    public String getHost()
    {
        return host;
    }

    @Override
    public Request host(String host)
    {
        this.host = host;
        this.uri = null;
        return this;
    }

    @Override
    public int getPort()
    {
        return port;
    }

    @Override
    public Request port(int port)
    {
        this.port = port;
        this.uri = null;
        return this;
    }

    @Override
    public Request transport(Transport transport)
    {
        this.transport = transport;
        return this;
    }

    @Override
    public Transport getTransport()
    {
        return transport;
    }

    @Override
    public String getMethod()
    {
        return method;
    }

    @Override
    public Request method(HttpMethod method)
    {
        return method(method.asString());
    }

    @Override
    public Request method(String method)
    {
        this.method = Objects.requireNonNull(method).toUpperCase(Locale.ENGLISH);
        return this;
    }

    @Override
    public String getPath()
    {
        return path;
    }

    @Override
    public Request path(String path)
    {
        URI uri = newURI(path);
        if (uri == null)
        {
            this.path = path;
            this.query = null;
        }
        else
        {
            String rawPath = uri.getRawPath();
            if (rawPath == null)
                rawPath = "";
            if (!rawPath.startsWith("/"))
                rawPath = "/" + rawPath;
            this.path = rawPath;
            String query = uri.getRawQuery();
            if (query != null)
            {
                this.query = query;
                params.clear();
                extractParams(query);
            }
            if (uri.isAbsolute())
                this.path = buildURI(false).toString();
        }
        this.uri = null;
        return this;
    }

    @Override
    public String getQuery()
    {
        return query;
    }

    @Override
    public URI getURI()
    {
        if (uri == null)
            uri = buildURI(true);

        @SuppressWarnings("ReferenceEquality")
        boolean isNullURI = (uri == NULL_URI);
        return isNullURI ? null : uri;
    }

    @Override
    public HttpVersion getVersion()
    {
        return version;
    }

    public boolean isVersionExplicit()
    {
        return versionExplicit;
    }

    @Override
    public Request version(HttpVersion version)
    {
        this.version = Objects.requireNonNull(version);
        this.versionExplicit = true;
        return this;
    }

    @Override
    public Request param(String name, String value)
    {
        return param(name, value, false);
    }

    private Request param(String name, String value, boolean fromQuery)
    {
        params.add(name, value);
        if (!fromQuery)
        {
            // If we have an existing query string, preserve it and append the new parameter.
            if (query != null)
                query += "&" + urlEncode(name) + "=" + urlEncode(value);
            else
                query = buildQuery();
            uri = null;
        }
        return this;
    }

    @Override
    public Fields getParams()
    {
        return params.asImmutable();
    }

    @Override
    public String getAgent()
    {
        return headers.get(HttpHeader.USER_AGENT);
    }

    @Override
    public Request agent(String agent)
    {
        headers.put(HttpHeader.USER_AGENT, agent);
        return this;
    }

    @Override
    public Request accept(String... accepts)
    {
        StringBuilder result = new StringBuilder();
        for (String accept : accepts)
        {
            if (result.length() > 0)
                result.append(", ");
            result.append(accept);
        }
        if (result.length() > 0)
            headers.put(HttpHeader.ACCEPT, result.toString());
        return this;
    }

    @Override
    public List getCookies()
    {
        return cookies != null ? cookies : Collections.emptyList();
    }

    @Override
    public Request cookie(HttpCookie cookie)
    {
        if (cookies == null)
            cookies = new ArrayList<>();
        cookies.add(cookie);
        return this;
    }

    @Override
    public Request tag(Object tag)
    {
        this.tag = tag;
        return this;
    }

    @Override
    public Object getTag()
    {
        return tag;
    }

    @Override
    public Request attribute(String name, Object value)
    {
        if (attributes == null)
            attributes = new HashMap<>(4);
        attributes.put(name, value);
        return this;
    }

    @Override
    public Map getAttributes()
    {
        return attributes != null ? attributes : Collections.emptyMap();
    }

    @Override
    public HttpFields getHeaders()
    {
        return headers;
    }

    @Override
    public Request headers(Consumer consumer)
    {
        consumer.accept(headers);
        return this;
    }

    public HttpRequest addHeader(HttpField header)
    {
        headers.add(header);
        return this;
    }

    private RequestListeners requestListeners()
    {
        if (requestListeners == null)
            requestListeners = new NotifyingRequestListeners();
        return requestListeners;
    }

    private NotifyingRequestListeners getHttpClientRequestListeners()
    {
        return (NotifyingRequestListeners)client.getRequestListeners();
    }

    @Override
    public Request listener(Request.Listener listener)
    {
        requestListeners().addListener(listener);
        return this;
    }

    @Override
    public Request onRequestQueued(QueuedListener listener)
    {
        requestListeners().addQueuedListener(listener);
        return this;
    }

    public void notifyQueued()
    {
        if (requestListeners != null)
            requestListeners.notifyQueued(this);
        getHttpClientRequestListeners().notifyQueued(this);
    }

    @Override
    public Request onRequestBegin(BeginListener listener)
    {
        requestListeners().addBeginListener(listener);
        return this;
    }

    public void notifyBegin()
    {
        if (requestListeners != null)
            requestListeners.notifyBegin(this);
        getHttpClientRequestListeners().notifyBegin(this);
    }

    @Override
    public Request onRequestHeaders(HeadersListener listener)
    {
        requestListeners().addHeadersListener(listener);
        return this;
    }

    public void notifyHeaders()
    {
        if (requestListeners != null)
            requestListeners.notifyHeaders(this);
        getHttpClientRequestListeners().notifyHeaders(this);
    }

    @Override
    public Request onRequestCommit(CommitListener listener)
    {
        requestListeners().addCommitListener(listener);
        return this;
    }

    public void notifyCommit()
    {
        if (requestListeners != null)
            requestListeners.notifyCommit(this);
        getHttpClientRequestListeners().notifyCommit(this);
    }

    @Override
    public Request onRequestContent(ContentListener listener)
    {
        requestListeners().addContentListener(listener);
        return this;
    }

    public void notifyContent(ByteBuffer byteBuffer)
    {
        if (requestListeners != null)
            requestListeners.notifyContent(this, byteBuffer);
        getHttpClientRequestListeners().notifyContent(this, byteBuffer);
    }

    @Override
    public Request onRequestSuccess(SuccessListener listener)
    {
        requestListeners().addSuccessListener(listener);
        return this;
    }

    public void notifySuccess()
    {
        if (requestListeners != null)
            requestListeners.notifySuccess(this);
        getHttpClientRequestListeners().notifySuccess(this);
    }

    @Override
    public Request onRequestFailure(FailureListener listener)
    {
        requestListeners().addFailureListener(listener);
        return this;
    }

    public void notifyFailure(Throwable failure)
    {
        if (requestListeners != null)
            requestListeners.notifyFailure(this, failure);
        getHttpClientRequestListeners().notifyFailure(this, failure);
    }

    @Override
    public Request onResponseBegin(Response.BeginListener listener)
    {
        responseListeners.addBeginListener(listener);
        return this;
    }

    @Override
    public Request onResponseHeader(Response.HeaderListener listener)
    {
        responseListeners.addHeaderListener(listener);
        return this;
    }

    @Override
    public Request onResponseHeaders(Response.HeadersListener listener)
    {
        responseListeners.addHeadersListener(listener);
        return this;
    }

    @Override
    public Request onResponseContent(Response.ContentListener listener)
    {
        responseListeners.addContentSourceListener(listener);
        return this;
    }

    @Override
    public Request onResponseContentAsync(Response.AsyncContentListener listener)
    {
        responseListeners.addContentSourceListener(listener);
        return this;
    }

    @Override
    public Request onResponseContentSource(Response.ContentSourceListener listener)
    {
        responseListeners.addContentSourceListener(listener);
        return this;
    }

    @Override
    public Request onResponseSuccess(Response.SuccessListener listener)
    {
        responseListeners.addSuccessListener(listener);
        return this;
    }

    @Override
    public Request onResponseFailure(Response.FailureListener listener)
    {
        responseListeners.addFailureListener(listener);
        return this;
    }

    @Override
    public Request onPush(BiFunction pushHandler)
    {
        this.pushHandler = pushHandler;
        return this;
    }

    @Override
    public Request onComplete(Response.CompleteListener listener)
    {
        responseListeners.addCompleteListener(listener, false);
        return this;
    }

    @Override
    public Request trailersSupplier(Supplier trailers)
    {
        this.trailers = trailers;
        return this;
    }

    @Override
    public Content getBody()
    {
        return content;
    }

    @Override
    public Request body(Content content)
    {
        this.content = content;
        return this;
    }

    @Override
    public Request file(Path file) throws IOException
    {
        return file(file, "application/octet-stream");
    }

    @Override
    public Request file(Path file, String contentType) throws IOException
    {
        return body(new PathRequestContent(contentType, file));
    }

    @Override
    public boolean isFollowRedirects()
    {
        return followRedirects;
    }

    @Override
    public Request followRedirects(boolean follow)
    {
        this.followRedirects = follow;
        return this;
    }

    @Override
    public long getIdleTimeout()
    {
        return idleTimeout;
    }

    @Override
    public Request idleTimeout(long timeout, TimeUnit unit)
    {
        this.idleTimeout = unit.toMillis(timeout);
        return this;
    }

    @Override
    public long getTimeout()
    {
        return timeout;
    }

    @Override
    public Request timeout(long timeout, TimeUnit unit)
    {
        this.timeout = unit.toMillis(timeout);
        return this;
    }

    @Override
    public ContentResponse send() throws InterruptedException, TimeoutException, ExecutionException
    {
        try
        {
            CompletableResponseListener listener = new CompletableResponseListener(this);
            return listener.send().get();
        }
        catch (ExecutionException x)
        {
            // Previously this method used a timed get on the future, which was in a
            // race with the timeouts implemented in HttpDestination and HttpConnection.
            // The change to make those timeouts relative to the timestamp taken in sent()
            // has made that race less certain, so a timeout could be either a TimeoutException
            // from the get() or a ExecutionException(TimeoutException) from the HttpDestination
            // or HttpConnection.
            // We now do not do a timed get and just rely on the HttpDestination/HttpConnection
            // timeouts.
            // This has the affect of changing this method from mostly throwing a TimeoutException
            // to always throwing an ExecutionException(TimeoutException).
            // Thus, for backwards compatibility we unwrap the TimeoutException here.
            if (x.getCause() instanceof TimeoutException t)
            {
                abort(t);
                throw t;
            }

            abort(x);
            throw x;
        }
        catch (Throwable x)
        {
            // Differently from the Future, the semantic of this method is that
            // if the send() is interrupted or times out, we abort the request.
            abort(x);
            throw x;
        }
    }

    @Override
    public void send(Response.CompleteListener listener)
    {
        Destination destination = client.resolveDestination(this);
        destination.send(this, listener);
    }

    void sendAsync(HttpDestination destination, Response.CompleteListener listener)
    {
        if (listener != null)
            responseListeners.addCompleteListener(listener, true);
        destination.send(this);
    }

    void sent()
    {
        if (timeoutNanoTime == Long.MAX_VALUE)
        {
            long timeout = getTimeout();
            if (timeout > 0)
                timeoutNanoTime = NanoTime.now() + TimeUnit.MILLISECONDS.toNanos(timeout);
        }
    }

    /**
     * @return The nanoTime at which the timeout expires or {@link Long#MAX_VALUE} if there is no timeout.
     * @see #timeout(long, TimeUnit)
     */
    public long getTimeoutNanoTime()
    {
        return timeoutNanoTime;
    }

    public ResponseListeners getResponseListeners()
    {
        return responseListeners;
    }

    public BiFunction getPushHandler()
    {
        return pushHandler;
    }

    @Override
    public Supplier getTrailersSupplier()
    {
        return trailers;
    }

    @Override
    public CompletableFuture abort(Throwable cause)
    {
        if (aborted.compareAndSet(null, Objects.requireNonNull(cause)))
        {
            Promise.Completable promise = new Promise.Completable<>();
            conversation.abort(cause, promise);
            return promise;
        }
        return CompletableFuture.completedFuture(false);
    }

    @Override
    public Throwable getAbortCause()
    {
        return aborted.get();
    }

    /**
     * 

Marks this request as normalized.

*

A request is normalized by setting things that applications give * for granted such as defaulting the method to {@code GET}, adding the * {@code Host} header, adding the cookies, adding {@code Authorization} * headers, etc.

* * @return whether this request was already normalized * @see HttpConnection#normalizeRequest(HttpRequest) */ boolean normalized() { boolean result = normalized; normalized = true; return result; } private String buildQuery() { StringBuilder result = new StringBuilder(); for (Iterator iterator = params.iterator(); iterator.hasNext(); ) { Fields.Field field = iterator.next(); List values = field.getValues(); for (int i = 0; i < values.size(); ++i) { if (i > 0) result.append("&"); result.append(field.getName()).append("="); result.append(urlEncode(values.get(i))); } if (iterator.hasNext()) result.append("&"); } return result.toString(); } private String urlEncode(String value) { if (value == null) return ""; return URLEncoder.encode(value, StandardCharsets.UTF_8); } private void extractParams(String query) { if (query != null) { for (String nameValue : query.split("&")) { String[] parts = nameValue.split("="); if (parts.length > 0) { String name = urlDecode(parts[0]); if (name.trim().length() == 0) continue; param(name, parts.length < 2 ? "" : urlDecode(parts[1]), true); } } } } private String urlDecode(String value) { return URLDecoder.decode(value, StandardCharsets.UTF_8); } private URI buildURI(boolean withQuery) { String path = getPath(); String query = getQuery(); if (query != null && withQuery) path += "?" + query; URI result = newURI(path); if (result == null) return NULL_URI; if (!result.isAbsolute()) result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path); return result; } private URI newURI(String path) { try { // Handle specially the "OPTIONS *" case, since it is possible to create a URI from "*" (!). if ("*".equals(path)) return null; URI result = new URI(path); return result.isOpaque() ? null : result; } catch (URISyntaxException x) { // The "path" of an HTTP request may not be a URI, // for example for CONNECT 127.0.0.1:8080. return null; } } @Override public String toString() { return String.format("%s[%s %s %s]@%x", getClass().getSimpleName(), getMethod(), getPath(), getVersion(), hashCode()); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy