You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@hc.apache.org by bu...@apache.org on 2003/03/10 22:56:21 UTC
DO NOT REPLY [Bug 17846] New: -
NullPointerException encountered in HttpMethodBase#getResponseBody()
DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG
RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT
<http://nagoya.apache.org/bugzilla/show_bug.cgi?id=17846>.
ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND
INSERTED IN THE BUG DATABASE.
http://nagoya.apache.org/bugzilla/show_bug.cgi?id=17846
NullPointerException encountered in HttpMethodBase#getResponseBody()
Summary: NullPointerException encountered in
HttpMethodBase#getResponseBody()
Product: Commons
Version: 2.0 Alpha 3
Platform: Sun
OS/Version: Solaris
Status: NEW
Severity: Normal
Priority: Other
Component: HttpClient
AssignedTo: commons-httpclient-dev@jakarta.apache.org
ReportedBy: javaguru3@yahoo.com
1. HttpMethodBase#execute(HttpState, HttpConnection) will enter the while loop
and call processRequest(HttpState, HttpConnection).
public int execute(HttpState state, HttpConnection conn)
throws HttpException, HttpRecoverableException,
IOException, NullPointerException {
LOG.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
// this is our connection now, assign it to a local variable so
// that it can be released later
this.responseConnection = conn;
checkExecuteConditions(state, conn);
inExecute = true;
try {
//pre-emptively add the authorization header, if required.
Authenticator.authenticate(this, state);
if (conn.isProxied()) {
Authenticator.authenticateProxy(this, state);
}
realms = new HashSet();
proxyRealms = new HashSet();
int forwardCount = 0; //protect from an infinite loop
while (forwardCount++ < MAX_FORWARDS) {
// on every retry, reset this state information.
conn.setLastResponseInputStream(null);
if (LOG.isDebugEnabled()) {
LOG.debug("Execute loop try " + forwardCount);
}
//write the request and read the response, will retry
processRequest(state, conn);
//if SC_CONTINUE write the request body
writeRemainingRequestBody(state, conn);
if (!isRetryNeeded(statusLine.getStatusCode(), state, conn)) {
// nope, no retry needed, exit loop.
break;
}
// retry - close previous stream. Caution - this causes
// responseBodyConsumed to be called, which may also close the
// connection.
if (responseStream != null) {
responseStream.close();
}
} //end of retry loop
if (forwardCount >= MAX_FORWARDS) {
LOG.error("Narrowly avoided an infinite loop in execute");
throw new HttpRecoverableException("Maximum redirects ("
+ MAX_FORWARDS + ") exceeded");
}
} finally {
inExecute = false;
// If the response has been fully processed, return the connection
// to the pool. Use this flag, rather than other tests (like
// responseStream == null), as subclasses, might reset the stream,
// for example, reading the entire response into a file and then
// setting the file as the stream.
if (doneWithConnection) {
ensureConnectionRelease();
}
}
return statusLine.getStatusCode();
}
2. processRequest(HttpState, HttpConnection) will call writeRequest(HttpState,
HttpConnection).
private void processRequest(HttpState state, HttpConnection connection)
throws HttpException, IOException {
LOG.trace(
"enter HttpMethodBase.processRequest(HttpState, HttpConnection)");
//try to do the write
int retryCount = 0;
do {
retryCount++;
if (LOG.isTraceEnabled()) {
LOG.trace("Attempt number " + retryCount + " to write
request");
}
try {
if (!connection.isOpen()) {
LOG.debug("Opening the connection.");
connection.open();
}
writeRequest(state, connection);
used = true; //write worked, mark this method as used
break; //move onto the write
} catch (HttpRecoverableException httpre) {
if (LOG.isDebugEnabled()) {
LOG.debug("Closing the connection.");
}
// update the recoverable exception count.
recoverableExceptionCount++;
connection.close();
LOG.info("Recoverable exception caught when writing request");
if (retryCount == maxRetries) {
LOG.warn(
"Attempt to write request has reached max retries: "
+ maxRetries);
throw httpre;
}
}
} while (retryCount <= maxRetries);
// Should we expect a response at this point?
boolean responseExpected = true;
if (!this.bodySent) {
if (connection.waitForResponse(RESPONSE_WAIT_TIME_MS)) {
responseExpected = true;
LOG.debug("Response available");
} else {
responseExpected = false;
// Something is wrong.
if (getRequestHeader("Expect") != null) {
// Most probably Expect header is not recongnized
// Remove the header to signal the method
// that it's okay to go ahead with sending data
removeRequestHeader("Expect");
}
LOG.debug("Response not available. Send the request body");
}
}
if (responseExpected) {
//try to do the read
try {
readResponse(state, connection);
} catch (HttpRecoverableException httpre) {
LOG.warn("Recoverable exception caught when reading response");
if (LOG.isDebugEnabled()) {
LOG.debug("Closing the connection.");
}
connection.close();
throw httpre;
}
}
//everything should be OK at this point
}
3. In writeRequest(HttpState, HttpConnection), call writeRequestLine
(HttpState, HttpConnection), writeRequestHeaders(HttpState, HttpConnection),
and then try to write the request body by calling
EntityEnclosingMethod#writeRequestBody(HttpState, HttpConnection). The
request header contains an "Expect", and statusLine is NULL because we are
expecting a response. Thus, writeRequestBody(HttpState, HttpConnection) will
return false, setting the bodySent to false.
protected void writeRequest(HttpState state, HttpConnection conn)
throws IOException, HttpException {
LOG.trace(
"enter HttpMethodBase.writeRequest(HttpState, HttpConnection)");
writeRequestLine(state, conn);
writeRequestHeaders(state, conn);
conn.writeLine(); // close head
bodySent = writeRequestBody(state, conn);
}
protected boolean writeRequestBody(HttpState state, HttpConnection conn)
throws IOException, HttpException {
LOG.trace(
"enter EntityEnclosingMethod.writeRequestBody(HttpState,
HttpConnection)");
if (getRequestHeader("Expect") != null) {
if (getStatusLine() == null) {
LOG.debug("Expecting response");
return false;
}
if (getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) {
LOG.debug("Expecting 100-continue");
return false;
}
}
int contentLength = getRequestContentLength();
if ((contentLength == CONTENT_LENGTH_CHUNKED) && !isHttp11()) {
throw new HttpException(
"Chunked transfer encoding not allowed for HTTP/1.0");
}
InputStream instream = getRequestBody();
if (instream == null) {
LOG.debug("Request body is empty");
return true;
}
if ((this.repeatCount > 0) && (this.buffer == null)) {
throw new HttpException(
"Unbuffered entity enclosing request can not be repeated.");
}
this.repeatCount++;
OutputStream outstream = conn.getRequestOutputStream();
if (contentLength == CONTENT_LENGTH_CHUNKED) {
outstream = new ChunkedOutputStream(outstream);
}
if (contentLength >= 0) {
// don't need a watcher here - we're reading from something local,
// not server-side.
instream = new ContentLengthInputStream(instream, contentLength);
}
byte[] tmp = new byte[4096];
int total = 0;
int i = 0;
while ((i = instream.read(tmp)) >= 0) {
outstream.write(tmp, 0, i);
total += i;
}
// This is hardly the most elegant solution to closing chunked stream
if (outstream instanceof ChunkedOutputStream) {
((ChunkedOutputStream) outstream).writeClosingChunk();
}
if ((contentLength > 0) && (total < contentLength)) {
throw new IOException("Unexpected end of input stream after "
+ total + " bytes (expected " + contentLength + " bytes)");
}
LOG.debug("Request body sent");
return true;
}
4. In processRequest(HttpState, HttpConnection), we wait for a response.
connection.waitForResponse(RESPONSE_WAIT_TIME_MS) returns false because 3
seconds has passed without a response being available.
5. Set responseExpected boolean to false, and removeRequestHeader("Expect") is
called. Also, since responseExpected is false, readResponse(HttpState,
HttpConnection) is not invoked, and statusLine, responseBody, and
responseStream are NULL.
6. In the while loop, in execute(HttpState, HttpConnection),
writeRemainingRequestBody(HttpState, HttpConnection) is called. Since the
statusLine is NULL, the writeRemaining is set to true. bodySent boolean is
false, so writeRequestBody() is called.
private void writeRemainingRequestBody(HttpState state,
HttpConnection connection)
throws HttpException, IOException {
LOG.trace("enter writeRemainingRequestBody(HttpState,
HttpConnection)");
boolean writeRemaining = false;
boolean continueReceived = false;
if (statusLine == null) {
writeRemaining = true;
} else {
if (statusLine.getStatusCode() == HttpStatus.SC_CONTINUE) {
writeRemaining = true;
continueReceived = true;
}
}
if (writeRemaining) {
if (!bodySent) {
bodySent = writeRequestBody(state, connection);
} else {
if (continueReceived) {
LOG.warn("Received status CONTINUE but the body has
already been sent");
// According to RFC 2616 this respose should be ignored
}
}
readResponse(state, connection);
}
}
7. In EntityEnclosingMethod#writeRequestBody(HttpState, HttpConnection),
getRequestHeader("Expect") will return NULL since it was previously removed.
The request body is written, so EntityEnclosingMethod#writeRequestBody
(HttpState, HttpConnection) returns true, setting bodySent in
writeRemainingRequestBody(HttpState, HttpConnection) to true.
8. readResponse(HttpState, HttpConnection) is called for the first time.
readStatusLine(HttpState, HttpConnection) is called, which in this case makes
the status 100-Continue. Although, we are not status 100-Continue at this
point. The problem is that we are reading the response that was not available
in step 4. Instead, we should be reading the response for the request body
written in step 7.
protected void readResponse(HttpState state, HttpConnection conn)
throws HttpException {
LOG.trace(
"enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
try {
readStatusLine(state, conn);
processStatusLine(state, conn);
readResponseHeaders(state, conn);
processResponseHeaders(state, conn);
readResponseBody(state, conn);
processResponseBody(state, conn);
} catch (IOException e) {
throw new HttpRecoverableException(e.toString());
}
}
protected void readStatusLine(HttpState state, HttpConnection conn)
throws IOException, HttpRecoverableException, HttpException {
LOG.trace("enter HttpMethodBase.readStatusLine(HttpState,
HttpConnection)");
//read out the HTTP status string
String statusString = conn.readLine();
while ((statusString != null) && !statusString.startsWith("HTTP/")) {
statusString = conn.readLine();
}
if (statusString == null) {
// A null statusString means the connection was lost before we got
a
// response. Try again.
throw new HttpRecoverableException("Error in parsing the status "
+ " line from the response: unable to find line starting with"
+ " \"HTTP/\"");
}
//create the status line from the status string
statusLine = new StatusLine(statusString);
//check for a valid HTTP-Version
String httpVersion = statusLine.getHttpVersion();
if (httpVersion.equals("HTTP/1.0")) {
http11 = false;
} else if (httpVersion.equals("HTTP/1.1")) {
http11 = true;
} else {
throw new HttpException("Unrecognized server protocol: '"
+ httpVersion + "'");
}
}
9. readResponse(HttpState, HttpConnection) calls readResponseBody(HttpState
state, HttpConnection conn), which calls readResponseBody(HttpConnection).
transferEncodingHeader and lengthHeader are both NULL, and canResponseHaveBody
(statusLine.getStatusCode()) returns false since the status is 100, which is
incorrect. So, readResponseBody(HttpConnection) returns NULL.
responseBodyConsumed() is called, and sets the responseStream to NULL. At
this point, responseBody is also NULL. writeRemainingRequestBody(HttpState,
HttpConnection) returns.
protected void readResponseBody(HttpState state, HttpConnection conn)
throws IOException, HttpException {
LOG.trace(
"enter HttpMethodBase.readResponseBody(HttpState,
HttpConnection)");
// assume we are not done with the connection if we get a stream
doneWithConnection = false;
InputStream stream = readResponseBody(conn);
if (stream == null) {
// done using the connection!
responseBodyConsumed();
} else {
conn.setLastResponseInputStream(stream);
setResponseStream(stream);
}
}
private InputStream readResponseBody(HttpConnection conn)
throws IOException {
LOG.trace("enter HttpMethodBase.readResponseBody(HttpConnection)");
LOG.debug("readResponseBody() setting responseBody to null");
responseBody = null; // is this desired?
Header lengthHeader = getResponseHeader("Content-Length");
Header transferEncodingHeader = getResponseHeader("Transfer-Encoding");
InputStream is = conn.getResponseInputStream();
if (WIRE_LOG.isDebugEnabled()) {
is = new WireLogInputStream(is);
}
InputStream result = null;
// We use Transfer-Encoding if present and ignore Content-Length.
// RFC2616, 4.4 item number 3
if (null != transferEncodingHeader) {
if ("chunked".equalsIgnoreCase(transferEncodingHeader.getValue()))
{
result = new ChunkedInputStream(is, this);
}
} else if (null != lengthHeader) {
// we're using this just in case the content length is duplicated
// i.e. '57, 57'
HeaderElement[] lengthElements = lengthHeader.getValues();
String lengthValue = null;
if (lengthElements.length > 1) {
// looks like the content length header was duplicated. if so
// they won't be key=value pairs so we just want to get
// the name not the value (which should be null)
// take the first value and ignore any others
lengthValue = lengthElements[0].getName();
} else {
lengthValue = lengthHeader.getValue();
}
if(statusLine != null) {
LOG.debug("readResponseBody(): lengthValue=" + lengthValue + ",
status=null");
} else {
LOG.debug("readResponseBody(): lengthValue=" + lengthValue + ",
status=" + statusLine.getStatusCode());
}
try {
int expectedLength = Integer.parseInt(lengthValue);
// FIXME: what if the content length is 0, perhaps we should
// just return an empty stream in that case
result = new ContentLengthInputStream(is, expectedLength);
} catch (NumberFormatException e) {
throw new HttpException(
"Unable to parse server response content length: '"
+ lengthValue + "'"
);
}
} else if (canResponseHaveBody(statusLine.getStatusCode())
&& !getName().equals(ConnectMethod.NAME)) {
result = is;
}
// if there is a result - ALWAYS wrap it in an observer which will
// close the underlying stream as soon as it is consumed, and notify
// the watcher that the stream has been consumed.
if (result != null) {
result = new AutoCloseInputStream(
result,
new ResponseConsumedWatcher() {
public void responseConsumed() {
responseBodyConsumed();
}
}
);
}
return result;
}
private static boolean canResponseHaveBody(int status) {
boolean result = true;
if ((status >= 100 && status <= 199) || (status == 204)
|| (status == 304)) { // NOT MODIFIED
result = false;
}
return result;
}
10. In the while loop, in execute(HttpState, HttpConnection), isRetryNeeded
(statusLine.getStatusCode(), state, conn) returns false, and causes the loop
to break. Then execute(HttpState, HttpConnection) retuns with the status code
100-Continue.
private boolean isRetryNeeded(int statusCode, HttpState state,
HttpConnection conn) {
switch (statusCode) {
case HttpStatus.SC_UNAUTHORIZED:
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
LOG.debug("Authorization required");
if (doAuthentication) { //process authentication response
//if the authentication is successful, return the
statusCode
//otherwise, drop through the switch and try again.
if (processAuthenticationResponse(state)) {
return false;
}
} else { //let the client handle the authenticaiton
return false;
}
break;
case HttpStatus.SC_MOVED_TEMPORARILY:
case HttpStatus.SC_MOVED_PERMANENTLY:
case HttpStatus.SC_SEE_OTHER:
case HttpStatus.SC_TEMPORARY_REDIRECT:
LOG.debug("Redirect required");
if (!processRedirectResponse(conn)) {
return false;
}
break;
default:
// neither an unauthorized nor a redirect response
return false;
} //end of switch
return true;
}
11. When we call getResponseBody(), getResponseBodyAsStream() returns NULL
since the responseStream and the responseBody are both NULL at this point.
When getResponseBody() tries to read the InputStream, a NullPointerException
is thrown since the InputStream is NULL.
To remedy this NullPointerException problem, I made the following changes to
my local copy of the code (for processRequest(HttpState, HttpConnection) and
writeRemainingRequestBody(HttpState, HttpConnection)). Any feedback, or an
alternative solution, on this would be appreciated.
private void processRequest(HttpState state, HttpConnection connection)
throws HttpException, IOException {
LOG.trace(
"enter HttpMethodBase.processRequest(HttpState, HttpConnection)");
//try to do the write
int retryCount = 0;
do {
retryCount++;
if (LOG.isTraceEnabled()) {
LOG.trace("Attempt number " + retryCount + " to write
request");
}
try {
if (!connection.isOpen()) {
LOG.debug("Opening the connection.");
connection.open();
}
writeRequest(state, connection);
used = true; //write worked, mark this method as used
break; //move onto the write
} catch (HttpRecoverableException httpre) {
if (LOG.isDebugEnabled()) {
LOG.debug("Closing the connection.");
}
// update the recoverable exception count.
recoverableExceptionCount++;
connection.close();
LOG.info("Recoverable exception caught when writing request");
if (retryCount == maxRetries) {
LOG.warn(
"Attempt to write request has reached max retries: "
+ maxRetries);
throw httpre;
}
}
} while (retryCount <= maxRetries);
// Should we expect a response at this point?
boolean responseExpected = true;
if (!this.bodySent) {
////////////////////// ADDITION STARTS HERE //////////////////////
boolean resReady = false;
int attemptCount = 0;
while(attemptCount < RESPONSE_WAIT_LIMIT) {
resReady = connection.waitForResponse(RESPONSE_WAIT_TIME_MS);
LOG.debug("waiting for response, attempt # " + (attemptCount+1)
+ ": ready? " + resReady);
if(resReady) {
break;
}
attemptCount++;
}
if (resReady) {
responseExpected = true;
LOG.debug("Response available");
} else {
responseExpected = false;
// Something is wrong.
if (getRequestHeader("Expect") != null) {
// Most probably Expect header is not recongnized
// Remove the header to signal the method
// that it's okay to go ahead with sending data
removeRequestHeader("Expect");
}
LOG.debug("Response not available. Send the request body");
}
////////////////////// ADDITION ENDS HERE //////////////////////
////////////////////// COMMENT BELOW OUT //////////////////////
// if (connection.waitForResponse(RESPONSE_WAIT_TIME_MS)) {
// responseExpected = true;
// LOG.debug("Response available");
// } else {
// responseExpected = false;
//
// // Something is wrong.
// if (getRequestHeader("Expect") != null) {
// // Most probably Expect header is not recongnized
// // Remove the header to signal the method
// // that it's okay to go ahead with sending data
// removeRequestHeader("Expect");
// }
// LOG.debug("Response not available. Send the request body");
// }
////////////////////// COMMENT ABOVE OUT //////////////////////
}
if (responseExpected) {
//try to do the read
try {
readResponse(state, connection);
} catch (HttpRecoverableException httpre) {
LOG.warn("Recoverable exception caught when reading response");
if (LOG.isDebugEnabled()) {
LOG.debug("Closing the connection.");
}
connection.close();
throw httpre;
}
}
//everything should be OK at this point
}
private void writeRemainingRequestBody(HttpState state,
HttpConnection connection)
throws HttpException, IOException {
LOG.trace("enter writeRemainingRequestBody(HttpState,
HttpConnection)");
boolean writeRemaining = false;
boolean continueReceived = false;
if (statusLine == null) {
writeRemaining = true;
} else {
if (statusLine.getStatusCode() == HttpStatus.SC_CONTINUE) {
writeRemaining = true;
continueReceived = true;
}
}
if (writeRemaining) {
if (!bodySent) {
bodySent = writeRequestBody(state, connection);
} else {
if (continueReceived) {
LOG.warn("Received status CONTINUE but the body has
already been sent");
// According to RFC 2616 this respose should be ignored
}
}
readResponse(state, connection);
////////////////////// ADDITION STARTS HERE //////////////////////
if(statusLine != null &&
statusLine.getStatusCode() == HttpStatus.SC_CONTINUE) {
LOG.debug("writeRemainingRequestBody(): status code is
HttpStatus.SC_CONTINUE, try reading another response");
try {
readResponse(state, connection);
if(statusLine == null) {
LOG.debug("writeRemainingRequestBody(): status line after
second readResponse is null.");
} else {
LOG.debug("writeRemainingRequestBody(): status code after
second readResponse: " + statusLine.getStatusCode());
}
} catch(HttpException httpe) {
LOG.error("writeRemainingRequestBody(): while reading another
response", httpe);
} catch(Throwable t) {
LOG.error("writeRemainingRequestBody(): while reading another
response", t);
}
}
////////////////////// ADDITION ENDS HERE //////////////////////
}
}
Thank you,
Randy