ViaRequest.java

/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */
package org.apache.hc.core5.http.protocol;

import java.io.IOException;

import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.ProtocolVersion;
import org.apache.hc.core5.net.URIAuthority;
import org.apache.hc.core5.util.Args;


/**
 * This request interceptor can be used by an HTTP proxy or intemediary to add the
 * {@link HttpHeaders#VIA} HTTP header to outgoing request messages.
 * <p>The {@link  HttpHeaders#VIA} header is used to indicate intermediate protocols and recipients
 * between the user agent and the server (on requests) or between the origin server and the client
 * (on responses). It can be used for tracking message forwards, avoiding request loops, and
 * identifying the protocol capabilities of senders along the request/response chain. Each member of
 * the {@link HttpHeaders#VIA} header field value represents a proxy or gateway that has forwarded
 * the message.
 * <p>A proxy <b>MUST</b> send an appropriate {@link  HttpHeaders#VIA} header field, as described
 * in
 * the HTTP specification, in each message that it forwards. An HTTP-to-HTTP gateway <b>MUST</b>
 * send an appropriate {@link HttpHeaders#VIA} header field in each inbound request message and
 * <b>MAY</b> send a {@link HttpHeaders#VIA} header field in forwarded response messages.
 * <p>This interceptor ensures that the {@link  HttpHeaders#VIA} header is added to the request
 * only
 * if it has not been added previously, as per the HTTP specification. Additionally, it updates the
 * values in the {@link HttpHeaders#VIA} header correctly in case of multiple intermediate protocols
 * or recipients, by appending its own information about how the message was received to the end of
 * the header field value.
 *
 * @since 5.3
 */
@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class ViaRequest implements HttpRequestInterceptor {


    /**
     * Singleton instance.
     */
    public static final HttpRequestInterceptor INSTANCE = new ViaRequest();

    /**
     * Constructs a new {@code ViaRequest}.
     */
    public ViaRequest() {
    }

    /**
     * Adds the HTTP {@link  HttpHeaders#VIA} header to the request if it does not already exist.
     *
     * <p>This method ensures that the version of the request is HTTP/1.1 or higher, and adds the
     * Via header in the format {@code <protocol> <version> <host>}, where {@code <protocol>} is the protocol name,
     * {@code <version>} is the major and minor version of the request, and {@code <host>} is the value of the Host header.
     *
     * <p>In case the {@link  HttpHeaders#VIA} header already exists, this method updates its value by appending
     * the new protocol information in the same format.
     *
     * <p>If the version of the request is lower than {@code HTTP/1.1} or the request authority not being specified,
     * this method throws a {@link ProtocolException}.
     *
     * @param request the request object to modify
     * @param entity the entity for the request, may be {@code null}
     * @param context the context for the request
     * @throws ProtocolException if there was a protocol error, such as the request version being lower than {@code HTTP/1.1},
     *         or the request authority not being specified
     * @throws IOException if there was an I/O error
     */
    @Override
    public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws ProtocolException, IOException {
        Args.notNull(request, "HTTP request");
        Args.notNull(context, "HTTP context");
        final ProtocolVersion ver = context.getProtocolVersion() != null ? context.getProtocolVersion() : HttpVersion.HTTP_1_1;

        final URIAuthority authority = request.getAuthority();
        if (authority == null) {
            throw new ProtocolException("Request authority not specified");
        }


        if (!ver.greaterEquals(HttpVersion.HTTP_1_1)) {
            throw new ProtocolException("Invalid protocol version: %s", ver);
        }
        if (!request.containsHeader(HttpHeaders.VIA)) {
            String viaHeaderValue = ver.getProtocol() + " " + ver.getMajor() + "." + ver.getMinor() + " " + authority.getHostName();
            final int port = authority.getPort();
            if (port != -1) {
                viaHeaderValue += ":" + port;
            }
            request.addHeader(HttpHeaders.VIA, viaHeaderValue);
        }
    }
}