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
28 package org.apache.http.impl;
29
30 import java.io.IOException;
31 import java.net.InetAddress;
32 import java.net.InetSocketAddress;
33 import java.net.Socket;
34 import java.net.SocketAddress;
35 import java.net.SocketException;
36
37 import org.apache.http.HttpInetConnection;
38 import org.apache.http.annotation.NotThreadSafe;
39 import org.apache.http.impl.io.SocketInputBuffer;
40 import org.apache.http.impl.io.SocketOutputBuffer;
41 import org.apache.http.io.SessionInputBuffer;
42 import org.apache.http.io.SessionOutputBuffer;
43 import org.apache.http.params.HttpConnectionParams;
44 import org.apache.http.params.HttpParams;
45
46 /**
47 * Implementation of a client-side HTTP connection that can be bound to an
48 * arbitrary {@link Socket} for receiving data from and transmitting data to
49 * a remote server.
50 * <p>
51 * The following parameters can be used to customize the behavior of this
52 * class:
53 * <ul>
54 * <li>{@link org.apache.http.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li>
55 * <li>{@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li>
56 * <li>{@link org.apache.http.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li>
57 * <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
58 * <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li>
59 * <li>{@link org.apache.http.params.CoreConnectionPNames#MIN_CHUNK_LIMIT}</li>
60 * </ul>
61 *
62 * @since 4.0
63 */
64 @NotThreadSafe
65 public class SocketHttpClientConnection
66 extends AbstractHttpClientConnection implements HttpInetConnection {
67
68 private volatile boolean open;
69 private volatile Socket socket = null;
70
71 public SocketHttpClientConnection() {
72 super();
73 }
74
75 protected void assertNotOpen() {
76 if (this.open) {
77 throw new IllegalStateException("Connection is already open");
78 }
79 }
80
81 @Override
82 protected void assertOpen() {
83 if (!this.open) {
84 throw new IllegalStateException("Connection is not open");
85 }
86 }
87
88 /**
89 * Creates an instance of {@link SocketInputBuffer} to be used for
90 * receiving data from the given {@link Socket}.
91 * <p>
92 * This method can be overridden in a super class in order to provide
93 * a custom implementation of {@link SessionInputBuffer} interface.
94 *
95 * @see SocketInputBuffer#SocketInputBuffer(Socket, int, HttpParams)
96 *
97 * @param socket the socket.
98 * @param buffersize the buffer size.
99 * @param params HTTP parameters.
100 * @return session input buffer.
101 * @throws IOException in case of an I/O error.
102 */
103 protected SessionInputBuffer createSessionInputBuffer(
104 final Socket socket,
105 int buffersize,
106 final HttpParams params) throws IOException {
107 return new SocketInputBuffer(socket, buffersize, params);
108 }
109
110 /**
111 * Creates an instance of {@link SessionOutputBuffer} to be used for
112 * sending data to the given {@link Socket}.
113 * <p>
114 * This method can be overridden in a super class in order to provide
115 * a custom implementation of {@link SocketOutputBuffer} interface.
116 *
117 * @see SocketOutputBuffer#SocketOutputBuffer(Socket, int, HttpParams)
118 *
119 * @param socket the socket.
120 * @param buffersize the buffer size.
121 * @param params HTTP parameters.
122 * @return session output buffer.
123 * @throws IOException in case of an I/O error.
124 */
125 protected SessionOutputBuffer createSessionOutputBuffer(
126 final Socket socket,
127 int buffersize,
128 final HttpParams params) throws IOException {
129 return new SocketOutputBuffer(socket, buffersize, params);
130 }
131
132 /**
133 * Binds this connection to the given {@link Socket}. This socket will be
134 * used by the connection to send and receive data.
135 * <p>
136 * This method will invoke {@link #createSessionInputBuffer(Socket, int, HttpParams)}
137 * and {@link #createSessionOutputBuffer(Socket, int, HttpParams)} methods
138 * to create session input / output buffers bound to this socket and then
139 * will invoke {@link #init(SessionInputBuffer, SessionOutputBuffer, HttpParams)}
140 * method to pass references to those buffers to the underlying HTTP message
141 * parser and formatter.
142 * <p>
143 * After this method's execution the connection status will be reported
144 * as open and the {@link #isOpen()} will return <code>true</code>.
145 *
146 * @param socket the socket.
147 * @param params HTTP parameters.
148 * @throws IOException in case of an I/O error.
149 */
150 protected void bind(
151 final Socket socket,
152 final HttpParams params) throws IOException {
153 if (socket == null) {
154 throw new IllegalArgumentException("Socket may not be null");
155 }
156 if (params == null) {
157 throw new IllegalArgumentException("HTTP parameters may not be null");
158 }
159 this.socket = socket;
160
161 int buffersize = HttpConnectionParams.getSocketBufferSize(params);
162
163 init(
164 createSessionInputBuffer(socket, buffersize, params),
165 createSessionOutputBuffer(socket, buffersize, params),
166 params);
167
168 this.open = true;
169 }
170
171 public boolean isOpen() {
172 return this.open;
173 }
174
175 protected Socket getSocket() {
176 return this.socket;
177 }
178
179 public InetAddress getLocalAddress() {
180 if (this.socket != null) {
181 return this.socket.getLocalAddress();
182 } else {
183 return null;
184 }
185 }
186
187 public int getLocalPort() {
188 if (this.socket != null) {
189 return this.socket.getLocalPort();
190 } else {
191 return -1;
192 }
193 }
194
195 public InetAddress getRemoteAddress() {
196 if (this.socket != null) {
197 return this.socket.getInetAddress();
198 } else {
199 return null;
200 }
201 }
202
203 public int getRemotePort() {
204 if (this.socket != null) {
205 return this.socket.getPort();
206 } else {
207 return -1;
208 }
209 }
210
211 public void setSocketTimeout(int timeout) {
212 assertOpen();
213 if (this.socket != null) {
214 try {
215 this.socket.setSoTimeout(timeout);
216 } catch (SocketException ignore) {
217 // It is not quite clear from the Sun's documentation if there are any
218 // other legitimate cases for a socket exception to be thrown when setting
219 // SO_TIMEOUT besides the socket being already closed
220 }
221 }
222 }
223
224 public int getSocketTimeout() {
225 if (this.socket != null) {
226 try {
227 return this.socket.getSoTimeout();
228 } catch (SocketException ignore) {
229 return -1;
230 }
231 } else {
232 return -1;
233 }
234 }
235
236 public void shutdown() throws IOException {
237 this.open = false;
238 Socket tmpsocket = this.socket;
239 if (tmpsocket != null) {
240 tmpsocket.close();
241 }
242 }
243
244 public void close() throws IOException {
245 if (!this.open) {
246 return;
247 }
248 this.open = false;
249 Socket sock = this.socket;
250 try {
251 doFlush();
252 try {
253 try {
254 sock.shutdownOutput();
255 } catch (IOException ignore) {
256 }
257 try {
258 sock.shutdownInput();
259 } catch (IOException ignore) {
260 }
261 } catch (UnsupportedOperationException ignore) {
262 // if one isn't supported, the other one isn't either
263 }
264 } finally {
265 sock.close();
266 }
267 }
268
269 private static void formatAddress(final StringBuilder buffer, final SocketAddress socketAddress) {
270 if (socketAddress instanceof InetSocketAddress) {
271 InetSocketAddress addr = ((InetSocketAddress) socketAddress);
272 buffer.append(addr.getAddress() != null ? addr.getAddress().getHostAddress() :
273 addr.getAddress())
274 .append(':')
275 .append(addr.getPort());
276 } else {
277 buffer.append(socketAddress);
278 }
279 }
280
281 @Override
282 public String toString() {
283 if (this.socket != null) {
284 StringBuilder buffer = new StringBuilder();
285 SocketAddress remoteAddress = this.socket.getRemoteSocketAddress();
286 SocketAddress localAddress = this.socket.getLocalSocketAddress();
287 if (remoteAddress != null && localAddress != null) {
288 formatAddress(buffer, localAddress);
289 buffer.append("<->");
290 formatAddress(buffer, remoteAddress);
291 }
292 return buffer.toString();
293 } else {
294 return super.toString();
295 }
296 }
297
298 }