1 // 2 // ======================================================================== 3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. 4 // ------------------------------------------------------------------------ 5 // All rights reserved. This program and the accompanying materials 6 // are made available under the terms of the Eclipse Public License v1.0 7 // and Apache License v2.0 which accompanies this distribution. 8 // 9 // The Eclipse Public License is available at 10 // http://www.eclipse.org/legal/epl-v10.html 11 // 12 // The Apache License v2.0 is available at 13 // http://www.opensource.org/licenses/apache2.0.php 14 // 15 // You may elect to redistribute this code under either of these licenses. 16 // ======================================================================== 17 // 18 19 package org.eclipse.jetty.client; 20 21 import java.io.Closeable; 22 import java.nio.ByteBuffer; 23 import java.util.Collections; 24 import java.util.Iterator; 25 26 import org.eclipse.jetty.client.api.ContentProvider; 27 import org.eclipse.jetty.client.util.DeferredContentProvider; 28 import org.eclipse.jetty.util.BufferUtil; 29 import org.eclipse.jetty.util.Callback; 30 import org.eclipse.jetty.util.log.Log; 31 import org.eclipse.jetty.util.log.Logger; 32 33 /** 34 * {@link HttpContent} is a stateful, linear representation of the request content provided 35 * by a {@link ContentProvider} that can be traversed one-way to obtain content buffers to 36 * send to a HTTP server. 37 * <p /> 38 * {@link HttpContent} offers the notion of a one-way cursor to traverse the content. 39 * The cursor starts in a virtual "before" position and can be advanced using {@link #advance()} 40 * until it reaches a virtual "after" position where the content is fully consumed. 41 * <pre> 42 * +---+ +---+ +---+ +---+ +---+ 43 * | | | | | | | | | | 44 * +---+ +---+ +---+ +---+ +---+ 45 * ^ ^ ^ ^ 46 * | | --> advance() | | 47 * | | last | 48 * | | | 49 * before | after 50 * | 51 * current 52 * </pre> 53 * At each valid (non-before and non-after) cursor position, {@link HttpContent} provides the following state: 54 * <ul> 55 * <li>the buffer containing the content to send, via {@link #getByteBuffer()}</li> 56 * <li>a copy of the content buffer that can be used for notifications, via {@link #getContent()}</li> 57 * <li>whether the buffer to write is the last one, via {@link #isLast()}</li> 58 * </ul> 59 * {@link HttpContent} may not have content, if the related {@link ContentProvider} is {@code null}, and this 60 * is reflected by {@link #hasContent()}. 61 * <p /> 62 * {@link HttpContent} may have {@link DeferredContentProvider deferred content}, in which case {@link #advance()} 63 * moves the cursor to a position that provides {@code null} {@link #getByteBuffer() buffer} and 64 * {@link #getContent() content}. When the deferred content is available, a further call to {@link #advance()} 65 * will move the cursor to a position that provides non {@code null} buffer and content. 66 */ 67 public class HttpContent implements Callback, Closeable 68 { 69 private static final Logger LOG = Log.getLogger(HttpContent.class); 70 private static final ByteBuffer AFTER = ByteBuffer.allocate(0); 71 72 private final ContentProvider provider; 73 private final Iterator<ByteBuffer> iterator; 74 private ByteBuffer buffer; 75 private volatile ByteBuffer content; 76 77 public HttpContent(ContentProvider provider) 78 { 79 this.provider = provider; 80 this.iterator = provider == null ? Collections.<ByteBuffer>emptyIterator() : provider.iterator(); 81 } 82 83 /** 84 * @return whether there is any content at all 85 */ 86 public boolean hasContent() 87 { 88 return provider != null; 89 } 90 91 /** 92 * @return whether the cursor points to the last content 93 */ 94 public boolean isLast() 95 { 96 return !iterator.hasNext(); 97 } 98 99 /** 100 * @return the {@link ByteBuffer} containing the content at the cursor's position 101 */ 102 public ByteBuffer getByteBuffer() 103 { 104 return buffer; 105 } 106 107 /** 108 * @return a {@link ByteBuffer#slice()} of {@link #getByteBuffer()} at the cursor's position 109 */ 110 public ByteBuffer getContent() 111 { 112 return content; 113 } 114 115 /** 116 * Advances the cursor to the next block of content. 117 * <p /> 118 * The next block of content may be valid (which yields a non-null buffer 119 * returned by {@link #getByteBuffer()}), but may also be deferred 120 * (which yields a null buffer returned by {@link #getByteBuffer()}). 121 * <p /> 122 * If the block of content pointed by the new cursor position is valid, this method returns true. 123 * 124 * @return true if there is content at the new cursor's position, false otherwise. 125 */ 126 public boolean advance() 127 { 128 if (isLast()) 129 { 130 if (content != AFTER) 131 { 132 content = buffer = AFTER; 133 LOG.debug("Advanced content past last chunk"); 134 } 135 return false; 136 } 137 else 138 { 139 ByteBuffer buffer = this.buffer = iterator.next(); 140 if (LOG.isDebugEnabled()) 141 LOG.debug("Advanced content to {} chunk {}", isLast() ? "last" : "next", buffer); 142 content = buffer == null ? null : buffer.slice(); 143 return buffer != null; 144 } 145 } 146 147 /** 148 * @return whether the cursor has been advanced past the {@link #isLast() last} position. 149 */ 150 public boolean isConsumed() 151 { 152 return content == AFTER; 153 } 154 155 @Override 156 public void succeeded() 157 { 158 if (iterator instanceof Callback) 159 ((Callback)iterator).succeeded(); 160 } 161 162 @Override 163 public void failed(Throwable x) 164 { 165 if (iterator instanceof Callback) 166 ((Callback)iterator).failed(x); 167 } 168 169 @Override 170 public void close() 171 { 172 try 173 { 174 if (iterator instanceof Closeable) 175 ((Closeable)iterator).close(); 176 } 177 catch (Exception x) 178 { 179 LOG.ignore(x); 180 } 181 } 182 183 @Override 184 public String toString() 185 { 186 return String.format("%s@%x - has=%b,last=%b,consumed=%b,buffer=%s", 187 getClass().getSimpleName(), 188 hashCode(), 189 hasContent(), 190 isLast(), 191 isConsumed(), 192 BufferUtil.toDetailString(getContent())); 193 } 194 }