001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.impl.nio; 029 030import java.io.IOException; 031import java.nio.channels.SelectionKey; 032import java.nio.charset.CharsetDecoder; 033import java.nio.charset.CharsetEncoder; 034 035import org.apache.http.HttpEntity; 036import org.apache.http.HttpEntityEnclosingRequest; 037import org.apache.http.HttpException; 038import org.apache.http.HttpRequest; 039import org.apache.http.HttpResponse; 040import org.apache.http.HttpResponseFactory; 041import org.apache.http.config.MessageConstraints; 042import org.apache.http.entity.ContentLengthStrategy; 043import org.apache.http.impl.nio.codecs.DefaultHttpRequestWriter; 044import org.apache.http.impl.nio.codecs.DefaultHttpRequestWriterFactory; 045import org.apache.http.impl.nio.codecs.DefaultHttpResponseParser; 046import org.apache.http.impl.nio.codecs.DefaultHttpResponseParserFactory; 047import org.apache.http.nio.NHttpClientEventHandler; 048import org.apache.http.nio.NHttpClientHandler; 049import org.apache.http.nio.NHttpClientIOTarget; 050import org.apache.http.nio.NHttpMessageParser; 051import org.apache.http.nio.NHttpMessageParserFactory; 052import org.apache.http.nio.NHttpMessageWriter; 053import org.apache.http.nio.NHttpMessageWriterFactory; 054import org.apache.http.nio.reactor.EventMask; 055import org.apache.http.nio.reactor.IOSession; 056import org.apache.http.nio.reactor.SessionInputBuffer; 057import org.apache.http.nio.reactor.SessionOutputBuffer; 058import org.apache.http.nio.util.ByteBufferAllocator; 059import org.apache.http.params.HttpParamConfig; 060import org.apache.http.params.HttpParams; 061import org.apache.http.util.Args; 062 063/** 064 * Default implementation of the {@link org.apache.http.nio.NHttpClientConnection} 065 * interface. 066 * 067 * @since 4.0 068 */ 069@SuppressWarnings("deprecation") 070public class DefaultNHttpClientConnection 071 extends NHttpConnectionBase implements NHttpClientIOTarget { 072 073 protected final NHttpMessageParser<HttpResponse> responseParser; 074 protected final NHttpMessageWriter<HttpRequest> requestWriter; 075 076 /** 077 * Creates a new instance of this class given the underlying I/O session. 078 * 079 * @param session the underlying I/O session. 080 * @param responseFactory HTTP response factory. 081 * @param allocator byte buffer allocator. 082 * @param params HTTP parameters. 083 * 084 * @deprecated (4.3) use {@link DefaultNHttpClientConnection#DefaultNHttpClientConnection( 085 * IOSession, int, int, ByteBufferAllocator, CharsetDecoder, CharsetEncoder, 086 * MessageConstraints, ContentLengthStrategy, ContentLengthStrategy, 087 * NHttpMessageWriterFactory, NHttpMessageParserFactory)} 088 */ 089 @Deprecated 090 public DefaultNHttpClientConnection( 091 final IOSession session, 092 final HttpResponseFactory responseFactory, 093 final ByteBufferAllocator allocator, 094 final HttpParams params) { 095 super(session, allocator, params); 096 Args.notNull(responseFactory, "Response factory"); 097 this.responseParser = createResponseParser(this.inbuf, responseFactory, params); 098 this.requestWriter = createRequestWriter(this.outbuf, params); 099 this.hasBufferedInput = false; 100 this.hasBufferedOutput = false; 101 this.session.setBufferStatus(this); 102 } 103 104 /** 105 * Creates new instance DefaultNHttpClientConnection given the underlying I/O session. 106 * 107 * @param session the underlying I/O session. 108 * @param buffersize buffer size. Must be a positive number. 109 * @param fragmentSizeHint fragment size hint. 110 * @param allocator memory allocator. 111 * If {@code null} {@link org.apache.http.nio.util.HeapByteBufferAllocator#INSTANCE} 112 * will be used. 113 * @param chardecoder decoder to be used for decoding HTTP protocol elements. 114 * If {@code null} simple type cast will be used for byte to char conversion. 115 * @param charencoder encoder to be used for encoding HTTP protocol elements. 116 * If {@code null} simple type cast will be used for char to byte conversion. 117 * @param constraints Message constraints. If {@code null} 118 * {@link MessageConstraints#DEFAULT} will be used. 119 * @param incomingContentStrategy incoming content length strategy. If {@code null} 120 * {@link org.apache.http.impl.entity.LaxContentLengthStrategy#INSTANCE} will be used. 121 * @param outgoingContentStrategy outgoing content length strategy. If {@code null} 122 * {@link org.apache.http.impl.entity.StrictContentLengthStrategy#INSTANCE} will be used. 123 * 124 * @since 4.3 125 */ 126 public DefaultNHttpClientConnection( 127 final IOSession session, 128 final int buffersize, 129 final int fragmentSizeHint, 130 final ByteBufferAllocator allocator, 131 final CharsetDecoder chardecoder, 132 final CharsetEncoder charencoder, 133 final MessageConstraints constraints, 134 final ContentLengthStrategy incomingContentStrategy, 135 final ContentLengthStrategy outgoingContentStrategy, 136 final NHttpMessageWriterFactory<HttpRequest> requestWriterFactory, 137 final NHttpMessageParserFactory<HttpResponse> responseParserFactory) { 138 super(session, buffersize, fragmentSizeHint, allocator, chardecoder, charencoder, 139 constraints, incomingContentStrategy, outgoingContentStrategy); 140 this.requestWriter = (requestWriterFactory != null ? requestWriterFactory : 141 DefaultHttpRequestWriterFactory.INSTANCE).create(this.outbuf); 142 this.responseParser = (responseParserFactory != null ? responseParserFactory : 143 DefaultHttpResponseParserFactory.INSTANCE).create(this.inbuf, constraints); 144 } 145 146 /** 147 * @since 4.3 148 */ 149 public DefaultNHttpClientConnection( 150 final IOSession session, 151 final int buffersize, 152 final CharsetDecoder chardecoder, 153 final CharsetEncoder charencoder, 154 final MessageConstraints constraints) { 155 this(session, buffersize, buffersize, null, chardecoder, charencoder, constraints, 156 null, null, null, null); 157 } 158 159 /** 160 * @since 4.3 161 */ 162 public DefaultNHttpClientConnection(final IOSession session, final int buffersize) { 163 this(session, buffersize, buffersize, null, null, null, null, null, null, null, null); 164 } 165 166 /** 167 * Creates an instance of {@link NHttpMessageParser} to be used 168 * by this connection for parsing incoming {@link HttpResponse} messages. 169 * <p> 170 * This method can be overridden in a super class in order to provide 171 * a different implementation of the {@link NHttpMessageParser} interface. 172 * 173 * @return HTTP response parser. 174 * 175 * @deprecated (4.3) use constructor. 176 */ 177 @Deprecated 178 protected NHttpMessageParser<HttpResponse> createResponseParser( 179 final SessionInputBuffer buffer, 180 final HttpResponseFactory responseFactory, 181 final HttpParams params) { 182 // override in derived class to specify a line parser 183 final MessageConstraints constraints = HttpParamConfig.getMessageConstraints(params); 184 return new DefaultHttpResponseParser(buffer, null, responseFactory, constraints); 185 } 186 187 /** 188 * Creates an instance of {@link NHttpMessageWriter} to be used 189 * by this connection for writing out outgoing {@link HttpRequest} messages. 190 * <p> 191 * This method can be overridden by a super class in order to provide 192 * a different implementation of the {@link NHttpMessageWriter} interface. 193 * 194 * @return HTTP response parser. 195 * 196 * @deprecated (4.3) use constructor. 197 */ 198 @Deprecated 199 protected NHttpMessageWriter<HttpRequest> createRequestWriter( 200 final SessionOutputBuffer buffer, 201 final HttpParams params) { 202 // override in derived class to specify a line formatter 203 return new DefaultHttpRequestWriter(buffer, null); 204 } 205 206 /** 207 * @since 4.2 208 */ 209 protected void onResponseReceived(final HttpResponse response) { 210 } 211 212 /** 213 * @since 4.2 214 */ 215 protected void onRequestSubmitted(final HttpRequest request) { 216 } 217 218 @Override 219 public void resetInput() { 220 this.response = null; 221 this.contentDecoder = null; 222 this.responseParser.reset(); 223 } 224 225 @Override 226 public void resetOutput() { 227 this.request = null; 228 this.contentEncoder = null; 229 this.requestWriter.reset(); 230 } 231 232 public void consumeInput(final NHttpClientEventHandler handler) { 233 if (this.status != ACTIVE) { 234 this.session.clearEvent(EventMask.READ); 235 return; 236 } 237 try { 238 if (this.response == null) { 239 int bytesRead; 240 do { 241 bytesRead = this.responseParser.fillBuffer(this.session.channel()); 242 if (bytesRead > 0) { 243 this.inTransportMetrics.incrementBytesTransferred(bytesRead); 244 } 245 this.response = this.responseParser.parse(); 246 } while (bytesRead > 0 && this.response == null); 247 if (this.response != null) { 248 if (this.response.getStatusLine().getStatusCode() >= 200) { 249 final HttpEntity entity = prepareDecoder(this.response); 250 this.response.setEntity(entity); 251 this.connMetrics.incrementResponseCount(); 252 } 253 this.hasBufferedInput = this.inbuf.hasData(); 254 onResponseReceived(this.response); 255 handler.responseReceived(this); 256 if (this.contentDecoder == null) { 257 resetInput(); 258 } 259 } 260 if (bytesRead == -1 && !this.inbuf.hasData()) { 261 handler.endOfInput(this); 262 } 263 } 264 if (this.contentDecoder != null && (this.session.getEventMask() & SelectionKey.OP_READ) > 0) { 265 handler.inputReady(this, this.contentDecoder); 266 if (this.contentDecoder.isCompleted()) { 267 // Response entity received 268 // Ready to receive a new response 269 resetInput(); 270 } 271 } 272 } catch (final HttpException ex) { 273 resetInput(); 274 handler.exception(this, ex); 275 } catch (final Exception ex) { 276 handler.exception(this, ex); 277 } finally { 278 // Finally set buffered input flag 279 this.hasBufferedInput = this.inbuf.hasData(); 280 } 281 } 282 283 public void produceOutput(final NHttpClientEventHandler handler) { 284 try { 285 if (this.status == ACTIVE) { 286 if (this.contentEncoder == null && !this.outbuf.hasData()) { 287 handler.requestReady(this); 288 } 289 if (this.contentEncoder != null) { 290 handler.outputReady(this, this.contentEncoder); 291 if (this.contentEncoder.isCompleted()) { 292 resetOutput(); 293 } 294 } 295 } 296 if (this.outbuf.hasData()) { 297 final int bytesWritten = this.outbuf.flush(this.session.channel()); 298 if (bytesWritten > 0) { 299 this.outTransportMetrics.incrementBytesTransferred(bytesWritten); 300 } 301 } 302 if (!this.outbuf.hasData()) { 303 if (this.status == CLOSING) { 304 this.session.close(); 305 this.status = CLOSED; 306 resetOutput(); 307 } 308 } 309 } catch (final Exception ex) { 310 handler.exception(this, ex); 311 } finally { 312 // Finally set the buffered output flag 313 this.hasBufferedOutput = this.outbuf.hasData(); 314 } 315 } 316 317 @Override 318 public void submitRequest(final HttpRequest request) throws IOException, HttpException { 319 Args.notNull(request, "HTTP request"); 320 assertNotClosed(); 321 if (this.request != null) { 322 throw new HttpException("Request already submitted"); 323 } 324 onRequestSubmitted(request); 325 this.requestWriter.write(request); 326 this.hasBufferedOutput = this.outbuf.hasData(); 327 328 if (request instanceof HttpEntityEnclosingRequest 329 && ((HttpEntityEnclosingRequest) request).getEntity() != null) { 330 prepareEncoder(request); 331 this.request = request; 332 } 333 this.connMetrics.incrementRequestCount(); 334 this.session.setEvent(EventMask.WRITE); 335 } 336 337 @Override 338 public boolean isRequestSubmitted() { 339 return this.request != null; 340 } 341 342 @Override 343 public void consumeInput(final NHttpClientHandler handler) { 344 consumeInput(new NHttpClientEventHandlerAdaptor(handler)); 345 } 346 347 @Override 348 public void produceOutput(final NHttpClientHandler handler) { 349 produceOutput(new NHttpClientEventHandlerAdaptor(handler)); 350 } 351 352}