It's supposed to work like this: you call the BlobstoreService to get an upload url, supplying a callback URL. The client is redirected to the upload URL, sends the data, then when they've finished they will be redirected to the callback URL with a couple of parameters which represent the blobstore key.
The dev server behaves differently to the production server as we shall see.
Against production, my code gets as far as completing the upload, then instead of getting a redirect back to my callback URL, I get a 400 error response (implying something wrong with my request). How do I debug this in production? I don't know how to switch on logging for the blobstore.
So I tried to run it locally against the dev server. This time if I don't set the 'content-length' property, I get a 411 (content length not set). But if I try and set that property, I get 'IllegalStateException: Already connected'. Neither of these exceptions happen against production.
So I don't know where to go next. I need to either get it working against dev, in the hope that I can debug the blobstore locally, or work out why it's not working on the blobstore in production.
public void upload(String uri, File file) throws IOException {
HttpURLConnection conn=null;
HttpURLConnection conn2=null;
FileInputStream fileInputStream = null;
DataOutputStream dos=null;
try {
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
int bytesRead, bytesAvailable, bufferSize;
byte[] buffer;
int maxBufferSize = 1 * 1024 * 1024;
// open a URL connection to the Servlet
fileInputStream = new FileInputStream(file);
URL url = new URL(uri);
// Open a HTTP connection to the URL
conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true); // Allow Inputs
conn.setDoOutput(true); // Allow Outputs
conn.setUseCaches(false); // Don't use a Cached Copy
conn.setInstanceFollowRedirects(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("ENCTYPE", "multipart/form-data");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
// conn.setChunkedStreamingMode(1024);
conn.setRequestProperty("content-length", String.valueOf(file.length())); //caused IllegalStateException "Already connected" locally, but not remotely
log("Orignal URL: " + conn.getURL());
//conn.connect(); //TODO duplicates url.openConnection() above?
conn.getInputStream(); //so we can follow the redirect
String redirectedUrl = conn.getHeaderField("Location");
log("Redirected URL: " + redirectedUrl);
//this is horrible and messy but let's get it working then clean it up later
conn.disconnect();
url = new URL(redirectedUrl);
// Open a new HTTP connection to the URL
conn2 = (HttpURLConnection) url.openConnection();
conn2.setDoInput(true); // Allow Inputs
conn2.setDoOutput(true); // Allow Outputs
conn2.setUseCaches(false); // Don't use a Cached Copy
conn2.setInstanceFollowRedirects(false);
conn2.setRequestMethod("POST");
conn2.setRequestProperty("Connection", "Keep-Alive");
conn2.setRequestProperty("ENCTYPE", "multipart/form-data");
conn2.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
conn2.setChunkedStreamingMode(maxBufferSize);
conn2.setRequestProperty("Content-Length", String.valueOf(file.length()));
conn2.connect();
dos = new DataOutputStream(conn2.getOutputStream());
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\"myFile\";filename=\""+file.getName()+"\"" + lineEnd);
dos.writeBytes(lineEnd);
// create a buffer of maximum size
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize];
// read file and write it into form...
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
dos.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
// send multipart form data necesssary after file data...
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// Responses from the server (code and message)
int serverResponseCode = conn2.getResponseCode();
String serverResponseMessage = conn2.getResponseMessage();
//we are expecting another redirect here
log("aaaargh! 400 instead of 302! "+serverResponseCode+" to "+conn2.getHeaderField("Location"));
}catch (IOException e) {
log(e.getMessage());
throw e;
}catch(Exception e) {
log(e.getMessage());
throw new IOException(e);
} finally {
//close the streams //
if (dos!=null) {
try {
dos.flush();
dos.close();
}catch(IOException ioe){}
}
if (fileInputStream!=null)
try {
fileInputStream.close();
}catch(IOException ioe){}
if (conn!=null )
try {
conn.disconnect();
}catch(Exception ioe){}
if (conn2!=null)
try {
conn2.disconnect();
}catch(Exception ioe){}
}
}
NB the serverResponseMessage string above comes back from the production blobstore as "Bad Request"
Against production, my code gets as far as completing the upload, then instead of getting a redirect back to my callback URL