View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.core5.http.protocol;
28  
29  import java.io.IOException;
30  
31  import org.apache.hc.core5.annotation.Contract;
32  import org.apache.hc.core5.annotation.ThreadingBehavior;
33  import org.apache.hc.core5.http.EntityDetails;
34  import org.apache.hc.core5.http.HttpHeaders;
35  import org.apache.hc.core5.http.HttpRequest;
36  import org.apache.hc.core5.http.HttpRequestInterceptor;
37  import org.apache.hc.core5.http.HttpVersion;
38  import org.apache.hc.core5.http.ProtocolException;
39  import org.apache.hc.core5.http.ProtocolVersion;
40  import org.apache.hc.core5.net.URIAuthority;
41  import org.apache.hc.core5.util.Args;
42  
43  
44  /**
45   * This request interceptor can be used by an HTTP proxy or intemediary to add the
46   * {@link HttpHeaders#VIA} HTTP header to outgoing request messages.
47   * <p>The {@link  HttpHeaders#VIA} header is used to indicate intermediate protocols and recipients
48   * between the user agent and the server (on requests) or between the origin server and the client
49   * (on responses). It can be used for tracking message forwards, avoiding request loops, and
50   * identifying the protocol capabilities of senders along the request/response chain. Each member of
51   * the {@link HttpHeaders#VIA} header field value represents a proxy or gateway that has forwarded
52   * the message.
53   * <p>A proxy <b>MUST</b> send an appropriate {@link  HttpHeaders#VIA} header field, as described
54   * in
55   * the HTTP specification, in each message that it forwards. An HTTP-to-HTTP gateway <b>MUST</b>
56   * send an appropriate {@link HttpHeaders#VIA} header field in each inbound request message and
57   * <b>MAY</b> send a {@link HttpHeaders#VIA} header field in forwarded response messages.
58   * <p>This interceptor ensures that the {@link  HttpHeaders#VIA} header is added to the request
59   * only
60   * if it has not been added previously, as per the HTTP specification. Additionally, it updates the
61   * values in the {@link HttpHeaders#VIA} header correctly in case of multiple intermediate protocols
62   * or recipients, by appending its own information about how the message was received to the end of
63   * the header field value.
64   *
65   * @since 5.3
66   */
67  @Contract(threading = ThreadingBehavior.IMMUTABLE)
68  public class ViaRequest implements HttpRequestInterceptor {
69  
70  
71      /**
72       * Singleton instance.
73       */
74      public static final HttpRequestInterceptor INSTANCE = new ViaRequest();
75  
76      /**
77       * Constructs a new {@code ViaRequest}.
78       */
79      public ViaRequest() {
80      }
81  
82      /**
83       * Adds the HTTP {@link  HttpHeaders#VIA} header to the request if it does not already exist.
84       *
85       * <p>This method ensures that the version of the request is HTTP/1.1 or higher, and adds the
86       * Via header in the format {@code <protocol> <version> <host>}, where {@code <protocol>} is the protocol name,
87       * {@code <version>} is the major and minor version of the request, and {@code <host>} is the value of the Host header.
88       *
89       * <p>In case the {@link  HttpHeaders#VIA} header already exists, this method updates its value by appending
90       * the new protocol information in the same format.
91       *
92       * <p>If the version of the request is lower than {@code HTTP/1.1} or the request authority not being specified,
93       * this method throws a {@link ProtocolException}.
94       *
95       * @param request the request object to modify
96       * @param entity the entity for the request, may be {@code null}
97       * @param context the context for the request
98       * @throws ProtocolException if there was a protocol error, such as the request version being lower than {@code HTTP/1.1},
99       *         or the request authority not being specified
100      * @throws IOException if there was an I/O error
101      */
102     @Override
103     public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws ProtocolException, IOException {
104         Args.notNull(request, "HTTP request");
105         Args.notNull(context, "HTTP context");
106         final ProtocolVersion ver = context.getProtocolVersion() != null ? context.getProtocolVersion() : HttpVersion.HTTP_1_1;
107 
108         final URIAuthority authority = request.getAuthority();
109         if (authority == null) {
110             throw new ProtocolException("Request authority not specified");
111         }
112 
113 
114         if (!ver.greaterEquals(HttpVersion.HTTP_1_1)) {
115             throw new ProtocolException("Invalid protocol version: %s", ver);
116         }
117         if (!request.containsHeader(HttpHeaders.VIA)) {
118             String viaHeaderValue = ver.getProtocol() + " " + ver.getMajor() + "." + ver.getMinor() + " " + authority.getHostName();
119             final int port = authority.getPort();
120             if (port != -1) {
121                 viaHeaderValue += ":" + port;
122             }
123             request.addHeader(HttpHeaders.VIA, viaHeaderValue);
124         }
125     }
126 }
127