1. /*
  2. * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v 1.39 2004/07/03 14:27:03 olegk Exp $
  3. * $Revision: 1.39 $
  4. * $Date: 2004/07/03 14:27:03 $
  5. *
  6. * ====================================================================
  7. *
  8. * Copyright 2003-2004 The Apache Software Foundation
  9. *
  10. * Licensed under the Apache License, Version 2.0 (the "License");
  11. * you may not use this file except in compliance with the License.
  12. * You may obtain a copy of the License at
  13. *
  14. * http://www.apache.org/licenses/LICENSE-2.0
  15. *
  16. * Unless required by applicable law or agreed to in writing, software
  17. * distributed under the License is distributed on an "AS IS" BASIS,
  18. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  19. * See the License for the specific language governing permissions and
  20. * limitations under the License.
  21. * ====================================================================
  22. *
  23. * This software consists of voluntary contributions made by many
  24. * individuals on behalf of the Apache Software Foundation. For more
  25. * information on the Apache Software Foundation, please see
  26. * <http://www.apache.org/>.
  27. *
  28. */
  29. package org.apache.commons.httpclient.methods;
  30. import java.io.IOException;
  31. import java.io.InputStream;
  32. import java.io.OutputStream;
  33. import java.io.UnsupportedEncodingException;
  34. import org.apache.commons.httpclient.ChunkedOutputStream;
  35. import org.apache.commons.httpclient.Header;
  36. import org.apache.commons.httpclient.HttpConnection;
  37. import org.apache.commons.httpclient.HttpException;
  38. import org.apache.commons.httpclient.HttpState;
  39. import org.apache.commons.httpclient.HttpVersion;
  40. import org.apache.commons.httpclient.ProtocolException;
  41. import org.apache.commons.logging.Log;
  42. import org.apache.commons.logging.LogFactory;
  43. /**
  44. * This abstract class serves as a foundation for all HTTP methods
  45. * that can enclose an entity within requests
  46. *
  47. * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
  48. * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
  49. *
  50. * @since 2.0beta1
  51. * @version $Revision: 1.39 $
  52. */
  53. public abstract class EntityEnclosingMethod extends ExpectContinueMethod {
  54. // ----------------------------------------- Static variables/initializers
  55. /**
  56. * The content length will be calculated automatically. This implies
  57. * buffering of the content.
  58. * @deprecated Use {@link InputStreamRequestEntity#CONTENT_LENGTH_AUTO}.
  59. */
  60. public static final long CONTENT_LENGTH_AUTO = -2;
  61. /**
  62. * The request will use chunked transfer encoding. Content length is not
  63. * calculated and the content is not buffered.<br>
  64. * @deprecated Use {@link #setContentChunked(boolean)}.
  65. */
  66. public static final long CONTENT_LENGTH_CHUNKED = -1;
  67. /** LOG object for this class. */
  68. private static final Log LOG = LogFactory.getLog(EntityEnclosingMethod.class);
  69. /** The unbuffered request body, if any. */
  70. private InputStream requestStream = null;
  71. /** The request body as string, if any. */
  72. private String requestString = null;
  73. private RequestEntity requestEntity;
  74. /** Counts how often the request was sent to the server. */
  75. private int repeatCount = 0;
  76. /** The content length of the <code>requestBodyStream</code> or one of
  77. * <code>CONTENT_LENGTH_AUTO</code> and <code>CONTENT_LENGTH_CHUNKED</code>.
  78. */
  79. private long requestContentLength = CONTENT_LENGTH_AUTO;
  80. // ----------------------------------------------------------- Constructors
  81. /**
  82. * No-arg constructor.
  83. *
  84. * @since 2.0
  85. */
  86. public EntityEnclosingMethod() {
  87. super();
  88. setFollowRedirects(false);
  89. }
  90. /**
  91. * Constructor specifying a URI.
  92. *
  93. * @param uri either an absolute or relative URI
  94. *
  95. * @since 2.0
  96. */
  97. public EntityEnclosingMethod(String uri) {
  98. super(uri);
  99. setFollowRedirects(false);
  100. }
  101. /**
  102. * Returns <tt>true</tt> if there is a request body to be sent.
  103. *
  104. * <P>This method must be overridden by sub-classes that implement
  105. * alternative request content input methods
  106. * </p>
  107. *
  108. * @return boolean
  109. *
  110. * @since 2.0beta1
  111. */
  112. protected boolean hasRequestContent() {
  113. LOG.trace("enter EntityEnclosingMethod.hasRequestContent()");
  114. return (this.requestEntity != null)
  115. || (this.requestStream != null)
  116. || (this.requestString != null);
  117. }
  118. /**
  119. * Clears the request body.
  120. *
  121. * <p>This method must be overridden by sub-classes that implement
  122. * alternative request content input methods.</p>
  123. *
  124. * @since 2.0beta1
  125. */
  126. protected void clearRequestBody() {
  127. LOG.trace("enter EntityEnclosingMethod.clearRequestBody()");
  128. this.requestStream = null;
  129. this.requestString = null;
  130. this.requestEntity = null;
  131. }
  132. /**
  133. * Generates the request body.
  134. *
  135. * <p>This method must be overridden by sub-classes that implement
  136. * alternative request content input methods.</p>
  137. *
  138. * @return request body as an array of bytes. If the request content
  139. * has not been set, returns <tt>null</tt>.
  140. *
  141. * @since 2.0beta1
  142. */
  143. protected byte[] generateRequestBody() {
  144. LOG.trace("enter EntityEnclosingMethod.renerateRequestBody()");
  145. return null;
  146. }
  147. protected RequestEntity generateRequestEntity() {
  148. byte[] requestBody = generateRequestBody();
  149. if (requestBody != null) {
  150. // use the request body, if it exists.
  151. // this is just for backwards compatability
  152. this.requestEntity = new ByteArrayRequestEntity(requestBody);
  153. } else if (this.requestStream != null) {
  154. this.requestEntity = new InputStreamRequestEntity(
  155. requestStream,
  156. requestContentLength);
  157. this.requestStream = null;
  158. } else if (this.requestString != null) {
  159. String charset = getRequestCharSet();
  160. try {
  161. this.requestEntity = new StringRequestEntity(
  162. requestString, null, charset);
  163. } catch (UnsupportedEncodingException e) {
  164. if (LOG.isWarnEnabled()) {
  165. LOG.warn(charset + " not supported");
  166. }
  167. this.requestEntity = new StringRequestEntity(
  168. requestString);
  169. }
  170. }
  171. return this.requestEntity;
  172. }
  173. /**
  174. * Entity enclosing requests cannot be redirected without user intervention
  175. * according to RFC 2616.
  176. *
  177. * @return <code>false</code>.
  178. *
  179. * @since 2.0
  180. */
  181. public boolean getFollowRedirects() {
  182. return false;
  183. }
  184. /**
  185. * Entity enclosing requests cannot be redirected without user intervention
  186. * according to RFC 2616.
  187. *
  188. * @param followRedirects must always be <code>false</code>
  189. */
  190. public void setFollowRedirects(boolean followRedirects) {
  191. if (followRedirects == true) {
  192. throw new IllegalArgumentException("Entity enclosing requests cannot be redirected without user intervention");
  193. }
  194. super.setFollowRedirects(false);
  195. }
  196. /**
  197. * Sets length information about the request body.
  198. *
  199. * <p>
  200. * Note: If you specify a content length the request is unbuffered. This
  201. * prevents redirection and automatic retry if a request fails the first
  202. * time. This means that the HttpClient can not perform authorization
  203. * automatically but will throw an Exception. You will have to set the
  204. * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
  205. * </p>
  206. *
  207. * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
  208. * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
  209. * is specified the content will not be buffered internally and the
  210. * Content-Length header of the request will be used. In this case
  211. * the user is responsible to supply the correct content length.
  212. * If CONTENT_LENGTH_AUTO is specified the request will be buffered
  213. * before it is sent over the network.
  214. *
  215. * @deprecated Use {@link #setContentChunked(boolean)} or
  216. * {@link #setRequestEntity(RequestEntity)}
  217. */
  218. public void setRequestContentLength(int length) {
  219. LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
  220. this.requestContentLength = length;
  221. }
  222. /**
  223. * Returns the request's charset. The charset is parsed from the request entity's
  224. * content type, unless the content type header has been set manually.
  225. *
  226. * @see RequestEntity#getContentType()
  227. *
  228. * @since 3.0
  229. */
  230. public String getRequestCharSet() {
  231. if (getRequestHeader("Content-Type") == null) {
  232. // check the content type from request entity
  233. // We can't call getRequestEntity() since it will probably call
  234. // this method.
  235. if (this.requestEntity != null) {
  236. return getContentCharSet(
  237. new Header("Content-Type", requestEntity.getContentType()));
  238. } else {
  239. return super.getRequestCharSet();
  240. }
  241. } else {
  242. return super.getRequestCharSet();
  243. }
  244. }
  245. /**
  246. * Sets length information about the request body.
  247. *
  248. * <p>
  249. * Note: If you specify a content length the request is unbuffered. This
  250. * prevents redirection and automatic retry if a request fails the first
  251. * time. This means that the HttpClient can not perform authorization
  252. * automatically but will throw an Exception. You will have to set the
  253. * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
  254. * </p>
  255. *
  256. * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
  257. * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
  258. * is specified the content will not be buffered internally and the
  259. * Content-Length header of the request will be used. In this case
  260. * the user is responsible to supply the correct content length.
  261. * If CONTENT_LENGTH_AUTO is specified the request will be buffered
  262. * before it is sent over the network.
  263. *
  264. * @deprecated Use {@link #setContentChunked(boolean)} or
  265. * {@link #setRequestEntity(RequestEntity)}
  266. */
  267. public void setRequestContentLength(long length) {
  268. LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
  269. this.requestContentLength = length;
  270. }
  271. /**
  272. * Sets whether or not the content should be chunked.
  273. *
  274. * @param chunked <code>true</code> if the content should be chunked
  275. *
  276. * @since 3.0
  277. */
  278. public void setContentChunked(boolean chunked) {
  279. this.requestContentLength = chunked ? CONTENT_LENGTH_CHUNKED : CONTENT_LENGTH_AUTO;
  280. }
  281. /**
  282. * Returns the length of the request body.
  283. *
  284. * @return number of bytes in the request body
  285. */
  286. protected long getRequestContentLength() {
  287. LOG.trace("enter EntityEnclosingMethod.getRequestContentLength()");
  288. if (!hasRequestContent()) {
  289. return 0;
  290. }
  291. // TODO what to do about setting request content and content length
  292. if (this.requestContentLength != CONTENT_LENGTH_AUTO) {
  293. return this.requestContentLength;
  294. }
  295. if (this.requestEntity == null) {
  296. this.requestEntity = generateRequestEntity();
  297. }
  298. return (this.requestEntity == null) ? 0 : this.requestEntity.getContentLength();
  299. }
  300. /**
  301. * Populates the request headers map to with additional
  302. * {@link org.apache.commons.httpclient.Header headers} to be submitted to
  303. * the given {@link HttpConnection}.
  304. *
  305. * <p>
  306. * This implementation adds tt>Content-Length</tt> or <tt>Transfer-Encoding</tt>
  307. * headers.
  308. * </p>
  309. *
  310. * <p>
  311. * Subclasses may want to override this method to to add additional
  312. * headers, and may choose to invoke this implementation (via
  313. * <tt>super</tt>) to add the "standard" headers.
  314. * </p>
  315. *
  316. * @param state the {@link HttpState state} information associated with this method
  317. * @param conn the {@link HttpConnection connection} used to execute
  318. * this HTTP method
  319. *
  320. * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
  321. * can be recovered from.
  322. * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
  323. * cannot be recovered from.
  324. *
  325. * @see #writeRequestHeaders
  326. *
  327. * @since 3.0
  328. */
  329. protected void addRequestHeaders(HttpState state, HttpConnection conn)
  330. throws IOException, HttpException {
  331. LOG.trace("enter EntityEnclosingMethod.addRequestHeaders(HttpState, "
  332. + "HttpConnection)");
  333. super.addRequestHeaders(state, conn);
  334. addContentLengthRequestHeader(state, conn);
  335. // only use the content type of the request entity if it has not already been
  336. // set manually
  337. if (getRequestHeader("Content-Type") == null) {
  338. RequestEntity requestEntity = getRequestEntity();
  339. if (requestEntity != null && requestEntity.getContentType() != null) {
  340. setRequestHeader("Content-Type", requestEntity.getContentType());
  341. }
  342. }
  343. }
  344. /**
  345. * Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
  346. * request header, as long as no <tt>Content-Length</tt> request header
  347. * already exists.
  348. *
  349. * @param state current state of http requests
  350. * @param conn the connection to use for I/O
  351. *
  352. * @throws IOException when errors occur reading or writing to/from the
  353. * connection
  354. * @throws HttpException when a recoverable error occurs
  355. */
  356. protected void addContentLengthRequestHeader(HttpState state,
  357. HttpConnection conn)
  358. throws IOException, HttpException {
  359. LOG.trace("enter EntityEnclosingMethod.addContentLengthRequestHeader("
  360. + "HttpState, HttpConnection)");
  361. if ((getRequestHeader("content-length") == null)
  362. && (getRequestHeader("Transfer-Encoding") == null)) {
  363. long len = getRequestContentLength();
  364. if (len >= 0) {
  365. addRequestHeader("Content-Length", String.valueOf(len));
  366. } else if ((len == CONTENT_LENGTH_CHUNKED)
  367. && (getEffectiveVersion().greaterEquals(HttpVersion.HTTP_1_1))) {
  368. addRequestHeader("Transfer-Encoding", "chunked");
  369. }
  370. }
  371. }
  372. /**
  373. * Sets the request body to be the specified inputstream.
  374. *
  375. * @param body Request body content as {@link java.io.InputStream}
  376. *
  377. * @deprecated use {@link #setRequestEntity(RequestEntity)}
  378. */
  379. public void setRequestBody(InputStream body) {
  380. LOG.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)");
  381. clearRequestBody();
  382. this.requestStream = body;
  383. }
  384. /**
  385. * Sets the request body to be the specified string.
  386. * The string will be submitted, using the encoding
  387. * specified in the Content-Type request header.<br>
  388. * Example: <code>setRequestHeader("Content-type", "text/xml; charset=UTF-8");</code><br>
  389. * Would use the UTF-8 encoding.
  390. * If no charset is specified, the
  391. * {@link org.apache.commons.httpclient.HttpConstants#DEFAULT_CONTENT_CHARSET default}
  392. * content encoding is used (ISO-8859-1).
  393. *
  394. * @param body Request body content as a string
  395. *
  396. * @deprecated use {@link #setRequestEntity(RequestEntity)}
  397. */
  398. public void setRequestBody(String body) {
  399. LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)");
  400. clearRequestBody();
  401. this.requestString = body;
  402. }
  403. /**
  404. * Writes the request body to the given {@link HttpConnection connection}.
  405. *
  406. * @param state the {@link HttpState state} information associated with this method
  407. * @param conn the {@link HttpConnection connection} used to execute
  408. * this HTTP method
  409. *
  410. * @return <tt>true</tt>
  411. *
  412. * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
  413. * can be recovered from.
  414. * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
  415. * cannot be recovered from.
  416. */
  417. protected boolean writeRequestBody(HttpState state, HttpConnection conn)
  418. throws IOException, HttpException {
  419. LOG.trace(
  420. "enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)");
  421. if (!hasRequestContent()) {
  422. LOG.debug("Request body has not been specified");
  423. return true;
  424. }
  425. long contentLength = getRequestContentLength();
  426. if ((contentLength == CONTENT_LENGTH_CHUNKED)
  427. && getEffectiveVersion().lessEquals(HttpVersion.HTTP_1_0)) {
  428. throw new ProtocolException(
  429. "Chunked transfer encoding not allowed for " +
  430. getEffectiveVersion().toString());
  431. }
  432. this.requestEntity = generateRequestEntity();
  433. if (requestEntity == null) {
  434. LOG.debug("Request body is empty");
  435. return true;
  436. }
  437. if ((this.repeatCount > 0) && !requestEntity.isRepeatable()) {
  438. throw new ProtocolException(
  439. "Unbuffered entity enclosing request can not be repeated.");
  440. }
  441. this.repeatCount++;
  442. OutputStream outstream = conn.getRequestOutputStream();
  443. if (contentLength == CONTENT_LENGTH_CHUNKED) {
  444. outstream = new ChunkedOutputStream(outstream);
  445. }
  446. requestEntity.writeRequest(outstream);
  447. // This is hardly the most elegant solution to closing chunked stream
  448. if (outstream instanceof ChunkedOutputStream) {
  449. ((ChunkedOutputStream) outstream).finish();
  450. }
  451. outstream.flush();
  452. LOG.debug("Request body sent");
  453. return true;
  454. }
  455. /**
  456. * Recycles the HTTP method so that it can be used again.
  457. * Note that all of the instance variables will be reset
  458. * once this method has been called. This method will also
  459. * release the connection being used by this HTTP method.
  460. *
  461. * @see #releaseConnection()
  462. *
  463. * @deprecated no longer supported and will be removed in the future
  464. * version of HttpClient
  465. */
  466. public void recycle() {
  467. LOG.trace("enter EntityEnclosingMethod.recycle()");
  468. clearRequestBody();
  469. this.requestContentLength = CONTENT_LENGTH_AUTO;
  470. this.repeatCount = 0;
  471. super.recycle();
  472. }
  473. /**
  474. * @return Returns the requestEntity.
  475. *
  476. * @since 3.0
  477. */
  478. public RequestEntity getRequestEntity() {
  479. return generateRequestEntity();
  480. }
  481. /**
  482. * @param requestEntity The requestEntity to set.
  483. *
  484. * @since 3.0
  485. */
  486. public void setRequestEntity(RequestEntity requestEntity) {
  487. clearRequestBody();
  488. this.requestEntity = requestEntity;
  489. }
  490. }