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.net.InetAddress; 032import java.net.InetSocketAddress; 033import java.net.Socket; 034import java.net.SocketAddress; 035import java.nio.channels.ReadableByteChannel; 036import java.nio.channels.WritableByteChannel; 037import java.nio.charset.Charset; 038import java.nio.charset.CharsetDecoder; 039import java.nio.charset.CharsetEncoder; 040import java.nio.charset.CodingErrorAction; 041 042import org.apache.http.ConnectionClosedException; 043import org.apache.http.Consts; 044import org.apache.http.Header; 045import org.apache.http.HttpConnectionMetrics; 046import org.apache.http.HttpEntity; 047import org.apache.http.HttpException; 048import org.apache.http.HttpInetConnection; 049import org.apache.http.HttpMessage; 050import org.apache.http.HttpRequest; 051import org.apache.http.HttpResponse; 052import org.apache.http.config.MessageConstraints; 053import org.apache.http.entity.BasicHttpEntity; 054import org.apache.http.entity.ContentLengthStrategy; 055import org.apache.http.impl.HttpConnectionMetricsImpl; 056import org.apache.http.impl.entity.LaxContentLengthStrategy; 057import org.apache.http.impl.entity.StrictContentLengthStrategy; 058import org.apache.http.impl.io.HttpTransportMetricsImpl; 059import org.apache.http.impl.nio.codecs.ChunkDecoder; 060import org.apache.http.impl.nio.codecs.ChunkEncoder; 061import org.apache.http.impl.nio.codecs.IdentityDecoder; 062import org.apache.http.impl.nio.codecs.IdentityEncoder; 063import org.apache.http.impl.nio.codecs.LengthDelimitedDecoder; 064import org.apache.http.impl.nio.codecs.LengthDelimitedEncoder; 065import org.apache.http.impl.nio.reactor.SessionInputBufferImpl; 066import org.apache.http.impl.nio.reactor.SessionOutputBufferImpl; 067import org.apache.http.io.HttpTransportMetrics; 068import org.apache.http.nio.ContentDecoder; 069import org.apache.http.nio.ContentEncoder; 070import org.apache.http.nio.NHttpConnection; 071import org.apache.http.nio.reactor.EventMask; 072import org.apache.http.nio.reactor.IOSession; 073import org.apache.http.nio.reactor.SessionBufferStatus; 074import org.apache.http.nio.reactor.SessionInputBuffer; 075import org.apache.http.nio.reactor.SessionOutputBuffer; 076import org.apache.http.nio.reactor.SocketAccessor; 077import org.apache.http.nio.util.ByteBufferAllocator; 078import org.apache.http.params.CoreConnectionPNames; 079import org.apache.http.params.CoreProtocolPNames; 080import org.apache.http.params.HttpParams; 081import org.apache.http.protocol.HTTP; 082import org.apache.http.protocol.HttpContext; 083import org.apache.http.util.Args; 084import org.apache.http.util.CharsetUtils; 085import org.apache.http.util.NetUtils; 086 087/** 088 * This class serves as a base for all {@link NHttpConnection} implementations and provides 089 * functionality common to both client and server HTTP connections. 090 * 091 * @since 4.0 092 */ 093@SuppressWarnings("deprecation") 094public class NHttpConnectionBase 095 implements NHttpConnection, HttpInetConnection, SessionBufferStatus, SocketAccessor { 096 097 protected final ContentLengthStrategy incomingContentStrategy; 098 protected final ContentLengthStrategy outgoingContentStrategy; 099 100 protected final SessionInputBufferImpl inbuf; 101 protected final SessionOutputBufferImpl outbuf; 102 private final int fragmentSizeHint; 103 private final MessageConstraints constraints; 104 105 protected final HttpTransportMetricsImpl inTransportMetrics; 106 protected final HttpTransportMetricsImpl outTransportMetrics; 107 protected final HttpConnectionMetricsImpl connMetrics; 108 109 protected HttpContext context; 110 protected IOSession session; 111 protected SocketAddress remote; 112 protected volatile ContentDecoder contentDecoder; 113 protected volatile boolean hasBufferedInput; 114 protected volatile ContentEncoder contentEncoder; 115 protected volatile boolean hasBufferedOutput; 116 protected volatile HttpRequest request; 117 protected volatile HttpResponse response; 118 119 protected volatile int status; 120 121 /** 122 * Creates a new instance of this class given the underlying I/O session. 123 * 124 * @param session the underlying I/O session. 125 * @param allocator byte buffer allocator. 126 * @param params HTTP parameters. 127 * 128 * @deprecated (4.3) use 129 * {@link NHttpConnectionBase#NHttpConnectionBase(IOSession, int, int, ByteBufferAllocator, 130 * CharsetDecoder, CharsetEncoder, ContentLengthStrategy, ContentLengthStrategy)} 131 */ 132 @Deprecated 133 public NHttpConnectionBase( 134 final IOSession session, 135 final ByteBufferAllocator allocator, 136 final HttpParams params) { 137 super(); 138 Args.notNull(session, "I/O session"); 139 Args.notNull(params, "HTTP params"); 140 141 int buffersize = params.getIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, -1); 142 if (buffersize <= 0) { 143 buffersize = 4096; 144 } 145 int linebuffersize = buffersize; 146 if (linebuffersize > 512) { 147 linebuffersize = 512; 148 } 149 150 CharsetDecoder decoder = null; 151 CharsetEncoder encoder = null; 152 Charset charset = CharsetUtils.lookup( 153 (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET)); 154 if (charset != null) { 155 charset = Consts.ASCII; 156 decoder = charset.newDecoder(); 157 encoder = charset.newEncoder(); 158 final CodingErrorAction malformedCharAction = (CodingErrorAction) params.getParameter( 159 CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION); 160 final CodingErrorAction unmappableCharAction = (CodingErrorAction) params.getParameter( 161 CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION); 162 decoder.onMalformedInput(malformedCharAction).onUnmappableCharacter(unmappableCharAction); 163 encoder.onMalformedInput(malformedCharAction).onUnmappableCharacter(unmappableCharAction); 164 } 165 this.inbuf = new SessionInputBufferImpl(buffersize, linebuffersize, decoder, allocator); 166 this.outbuf = new SessionOutputBufferImpl(buffersize, linebuffersize, encoder, allocator); 167 this.fragmentSizeHint = buffersize; 168 this.constraints = MessageConstraints.DEFAULT; 169 170 this.incomingContentStrategy = createIncomingContentStrategy(); 171 this.outgoingContentStrategy = createOutgoingContentStrategy(); 172 173 this.inTransportMetrics = createTransportMetrics(); 174 this.outTransportMetrics = createTransportMetrics(); 175 this.connMetrics = createConnectionMetrics( 176 this.inTransportMetrics, 177 this.outTransportMetrics); 178 179 setSession(session); 180 this.status = ACTIVE; 181 } 182 183 /** 184 * Creates new instance NHttpConnectionBase given the underlying I/O session. 185 * 186 * @param session the underlying I/O session. 187 * @param buffersize buffer size. Must be a positive number. 188 * @param fragmentSizeHint fragment size hint. 189 * @param allocator memory allocator. 190 * If {@code null} {@link org.apache.http.nio.util.HeapByteBufferAllocator#INSTANCE} 191 * will be used. 192 * @param chardecoder decoder to be used for decoding HTTP protocol elements. 193 * If {@code null} simple type cast will be used for byte to char conversion. 194 * @param charencoder encoder to be used for encoding HTTP protocol elements. 195 * If {@code null} simple type cast will be used for char to byte conversion. 196 * @param constraints Message constraints. If {@code null} 197 * {@link MessageConstraints#DEFAULT} will be used. 198 * @param incomingContentStrategy incoming content length strategy. If {@code null} 199 * {@link LaxContentLengthStrategy#INSTANCE} will be used. 200 * @param outgoingContentStrategy outgoing content length strategy. If {@code null} 201 * {@link StrictContentLengthStrategy#INSTANCE} will be used. 202 * 203 * @since 4.4 204 */ 205 protected NHttpConnectionBase( 206 final IOSession session, 207 final int buffersize, 208 final int fragmentSizeHint, 209 final ByteBufferAllocator allocator, 210 final CharsetDecoder chardecoder, 211 final CharsetEncoder charencoder, 212 final MessageConstraints constraints, 213 final ContentLengthStrategy incomingContentStrategy, 214 final ContentLengthStrategy outgoingContentStrategy) { 215 Args.notNull(session, "I/O session"); 216 Args.positive(buffersize, "Buffer size"); 217 int linebuffersize = buffersize; 218 if (linebuffersize > 512) { 219 linebuffersize = 512; 220 } 221 this.inbuf = new SessionInputBufferImpl(buffersize, linebuffersize, chardecoder, allocator); 222 this.outbuf = new SessionOutputBufferImpl(buffersize, linebuffersize, charencoder, allocator); 223 this.fragmentSizeHint = fragmentSizeHint >= 0 ? fragmentSizeHint : buffersize; 224 225 this.inTransportMetrics = new HttpTransportMetricsImpl(); 226 this.outTransportMetrics = new HttpTransportMetricsImpl(); 227 this.connMetrics = new HttpConnectionMetricsImpl(this.inTransportMetrics, this.outTransportMetrics); 228 this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT; 229 this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : 230 LaxContentLengthStrategy.INSTANCE; 231 this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy : 232 StrictContentLengthStrategy.INSTANCE; 233 234 setSession(session); 235 this.status = ACTIVE; 236 } 237 238 /** 239 * Creates new instance NHttpConnectionBase given the underlying I/O session. 240 * 241 * @param session the underlying I/O session. 242 * @param buffersize buffer size. Must be a positive number. 243 * @param fragmentSizeHint fragment size hint. 244 * @param allocator memory allocator. 245 * If {@code null} {@link org.apache.http.nio.util.HeapByteBufferAllocator#INSTANCE} 246 * will be used. 247 * @param chardecoder decoder to be used for decoding HTTP protocol elements. 248 * If {@code null} simple type cast will be used for byte to char conversion. 249 * @param charencoder encoder to be used for encoding HTTP protocol elements. 250 * If {@code null} simple type cast will be used for char to byte conversion. 251 * @param incomingContentStrategy incoming content length strategy. If {@code null} 252 * {@link LaxContentLengthStrategy#INSTANCE} will be used. 253 * @param outgoingContentStrategy outgoing content length strategy. If {@code null} 254 * {@link StrictContentLengthStrategy#INSTANCE} will be used. 255 * 256 * @since 4.3 257 */ 258 protected NHttpConnectionBase( 259 final IOSession session, 260 final int buffersize, 261 final int fragmentSizeHint, 262 final ByteBufferAllocator allocator, 263 final CharsetDecoder chardecoder, 264 final CharsetEncoder charencoder, 265 final ContentLengthStrategy incomingContentStrategy, 266 final ContentLengthStrategy outgoingContentStrategy) { 267 this(session, buffersize, fragmentSizeHint, allocator, chardecoder, charencoder, 268 null, incomingContentStrategy, outgoingContentStrategy); 269 } 270 271 private void setSession(final IOSession session) { 272 this.session = session; 273 this.context = new SessionHttpContext(this.session); 274 this.session.setBufferStatus(this); 275 this.remote = this.session.getRemoteAddress(); 276 } 277 278 /** 279 * Binds the connection to a different {@link IOSession}. This may be necessary 280 * when the underlying I/O session gets upgraded with SSL/TLS encryption. 281 * 282 * @since 4.2 283 */ 284 protected void bind(final IOSession session) { 285 Args.notNull(session, "I/O session"); 286 setSession(session); 287 } 288 289 /** 290 * @since 4.2 291 * 292 * @deprecated (4.3) use constructor. 293 */ 294 @Deprecated 295 protected ContentLengthStrategy createIncomingContentStrategy() { 296 return new LaxContentLengthStrategy(); 297 } 298 299 /** 300 * @since 4.2 301 * 302 * @deprecated (4.3) use constructor. 303 */ 304 @Deprecated 305 protected ContentLengthStrategy createOutgoingContentStrategy() { 306 return new StrictContentLengthStrategy(); 307 } 308 309 /** 310 * @since 4.1 311 * 312 * @deprecated (4.3) no longer used. 313 */ 314 @Deprecated 315 protected HttpTransportMetricsImpl createTransportMetrics() { 316 return new HttpTransportMetricsImpl(); 317 } 318 319 /** 320 * @since 4.1 321 * 322 * @deprecated (4.3) use decorator to add additional metrics. 323 */ 324 @Deprecated 325 protected HttpConnectionMetricsImpl createConnectionMetrics( 326 final HttpTransportMetrics inTransportMetric, 327 final HttpTransportMetrics outTransportMetric) { 328 return new HttpConnectionMetricsImpl(inTransportMetric, outTransportMetric); 329 } 330 331 @Override 332 public int getStatus() { 333 return this.status; 334 } 335 336 @Override 337 public HttpContext getContext() { 338 return this.context; 339 } 340 341 @Override 342 public HttpRequest getHttpRequest() { 343 return this.request; 344 } 345 346 @Override 347 public HttpResponse getHttpResponse() { 348 return this.response; 349 } 350 351 @Override 352 public void requestInput() { 353 this.session.setEvent(EventMask.READ); 354 } 355 356 @Override 357 public void requestOutput() { 358 this.session.setEvent(EventMask.WRITE); 359 } 360 361 @Override 362 public void suspendInput() { 363 this.session.clearEvent(EventMask.READ); 364 } 365 366 @Override 367 public void suspendOutput() { 368 this.session.clearEvent(EventMask.WRITE); 369 } 370 371 /** 372 * Initializes a specific {@link ContentDecoder} implementation based on the 373 * properties of the given {@link HttpMessage} and generates an instance of 374 * {@link HttpEntity} matching the properties of the content decoder. 375 * 376 * @param message the HTTP message. 377 * @return HTTP entity. 378 * @throws HttpException in case of an HTTP protocol violation. 379 */ 380 protected HttpEntity prepareDecoder(final HttpMessage message) throws HttpException { 381 final BasicHttpEntity entity = new BasicHttpEntity(); 382 final long len = this.incomingContentStrategy.determineLength(message); 383 this.contentDecoder = createContentDecoder( 384 len, 385 this.session.channel(), 386 this.inbuf, 387 this.inTransportMetrics); 388 if (len == ContentLengthStrategy.CHUNKED) { 389 entity.setChunked(true); 390 entity.setContentLength(-1); 391 } else if (len == ContentLengthStrategy.IDENTITY) { 392 entity.setChunked(false); 393 entity.setContentLength(-1); 394 } else { 395 entity.setChunked(false); 396 entity.setContentLength(len); 397 } 398 399 final Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE); 400 if (contentTypeHeader != null) { 401 entity.setContentType(contentTypeHeader); 402 } 403 final Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING); 404 if (contentEncodingHeader != null) { 405 entity.setContentEncoding(contentEncodingHeader); 406 } 407 return entity; 408 } 409 410 /** 411 * Factory method for {@link ContentDecoder} instances. 412 * 413 * @param len content length, if known, {@link ContentLengthStrategy#CHUNKED} or 414 * {@link ContentLengthStrategy#IDENTITY}, if unknown. 415 * @param channel the session channel. 416 * @param buffer the session buffer. 417 * @param metrics transport metrics. 418 * 419 * @return content decoder. 420 * 421 * @since 4.1 422 */ 423 protected ContentDecoder createContentDecoder( 424 final long len, 425 final ReadableByteChannel channel, 426 final SessionInputBuffer buffer, 427 final HttpTransportMetricsImpl metrics) { 428 if (len == ContentLengthStrategy.CHUNKED) { 429 return new ChunkDecoder(channel, buffer, this.constraints, metrics); 430 } else if (len == ContentLengthStrategy.IDENTITY) { 431 return new IdentityDecoder(channel, buffer, metrics); 432 } else { 433 return new LengthDelimitedDecoder(channel, buffer, metrics, len); 434 } 435 } 436 437 /** 438 * Initializes a specific {@link ContentEncoder} implementation based on the 439 * properties of the given {@link HttpMessage}. 440 * 441 * @param message the HTTP message. 442 * @throws HttpException in case of an HTTP protocol violation. 443 */ 444 protected void prepareEncoder(final HttpMessage message) throws HttpException { 445 final long len = this.outgoingContentStrategy.determineLength(message); 446 this.contentEncoder = createContentEncoder( 447 len, 448 this.session.channel(), 449 this.outbuf, 450 this.outTransportMetrics); 451 } 452 453 /** 454 * Factory method for {@link ContentEncoder} instances. 455 * 456 * @param len content length, if known, {@link ContentLengthStrategy#CHUNKED} or 457 * {@link ContentLengthStrategy#IDENTITY}, if unknown. 458 * @param channel the session channel. 459 * @param buffer the session buffer. 460 * @param metrics transport metrics. 461 * 462 * @return content encoder. 463 * 464 * @since 4.1 465 */ 466 protected ContentEncoder createContentEncoder( 467 final long len, 468 final WritableByteChannel channel, 469 final SessionOutputBuffer buffer, 470 final HttpTransportMetricsImpl metrics) { 471 if (len == ContentLengthStrategy.CHUNKED) { 472 return new ChunkEncoder(channel, buffer, metrics, this.fragmentSizeHint); 473 } else if (len == ContentLengthStrategy.IDENTITY) { 474 return new IdentityEncoder(channel, buffer, metrics, this.fragmentSizeHint); 475 } else { 476 return new LengthDelimitedEncoder(channel, buffer, metrics, len, this.fragmentSizeHint); 477 } 478 } 479 480 @Override 481 public boolean hasBufferedInput() { 482 return this.hasBufferedInput; 483 } 484 485 @Override 486 public boolean hasBufferedOutput() { 487 return this.hasBufferedOutput; 488 } 489 490 /** 491 * Assets if the connection is still open. 492 * 493 * @throws ConnectionClosedException in case the connection has already 494 * been closed. 495 */ 496 protected void assertNotClosed() throws ConnectionClosedException { 497 if (this.status != ACTIVE) { 498 throw new ConnectionClosedException("Connection is closed"); 499 } 500 } 501 502 @Override 503 public void close() throws IOException { 504 if (this.status != ACTIVE) { 505 return; 506 } 507 this.status = CLOSING; 508 if (this.outbuf.hasData()) { 509 this.session.setEvent(EventMask.WRITE); 510 } else { 511 this.session.close(); 512 this.status = CLOSED; 513 } 514 } 515 516 @Override 517 public boolean isOpen() { 518 return this.status == ACTIVE && !this.session.isClosed(); 519 } 520 521 @Override 522 public boolean isStale() { 523 return this.session.isClosed(); 524 } 525 526 @Override 527 public InetAddress getLocalAddress() { 528 final SocketAddress address = this.session.getLocalAddress(); 529 if (address instanceof InetSocketAddress) { 530 return ((InetSocketAddress) address).getAddress(); 531 } else { 532 return null; 533 } 534 } 535 536 @Override 537 public int getLocalPort() { 538 final SocketAddress address = this.session.getLocalAddress(); 539 if (address instanceof InetSocketAddress) { 540 return ((InetSocketAddress) address).getPort(); 541 } else { 542 return -1; 543 } 544 } 545 546 @Override 547 public InetAddress getRemoteAddress() { 548 final SocketAddress address = this.session.getRemoteAddress(); 549 if (address instanceof InetSocketAddress) { 550 return ((InetSocketAddress) address).getAddress(); 551 } else { 552 return null; 553 } 554 } 555 556 @Override 557 public int getRemotePort() { 558 final SocketAddress address = this.session.getRemoteAddress(); 559 if (address instanceof InetSocketAddress) { 560 return ((InetSocketAddress) address).getPort(); 561 } else { 562 return -1; 563 } 564 } 565 566 @Override 567 public void setSocketTimeout(final int timeout) { 568 this.session.setSocketTimeout(timeout); 569 } 570 571 @Override 572 public int getSocketTimeout() { 573 return this.session.getSocketTimeout(); 574 } 575 576 @Override 577 public void shutdown() throws IOException { 578 this.status = CLOSED; 579 this.session.shutdown(); 580 } 581 582 @Override 583 public HttpConnectionMetrics getMetrics() { 584 return this.connMetrics; 585 } 586 587 @Override 588 public String toString() { 589 final SocketAddress remoteAddress = this.session.getRemoteAddress(); 590 final SocketAddress localAddress = this.session.getLocalAddress(); 591 if (remoteAddress != null && localAddress != null) { 592 final StringBuilder buffer = new StringBuilder(); 593 NetUtils.formatAddress(buffer, localAddress); 594 buffer.append("<->"); 595 NetUtils.formatAddress(buffer, remoteAddress); 596 return buffer.toString(); 597 } else { 598 return "[Not bound]"; 599 } 600 } 601 602 @Override 603 public Socket getSocket() { 604 if (this.session instanceof SocketAccessor) { 605 return ((SocketAccessor) this.session).getSocket(); 606 } else { 607 return null; 608 } 609 } 610 611}