7

I am trying to upload file (multi part form data) using HTTPHandler.

WebKit Boundary is getting written to the destination file, thus corrupting the file.

Input files can be any kind of files, including text, zip, apk etc.

Code:

    public void handle(HttpExchange httpExchange) throws IOException {

    URI uri = httpExchange.getRequestURI();
    String httpReqMethod = httpExchange.getRequestMethod();
    Headers headers = httpExchange.getRequestHeaders();
    InputStream  inputStrm = null;
    FileOutputStream destFile = null;
    String contentType = ((headers.get("Content-type") != null) ? (headers.get("Content-type").toString()) : (null));
    httpExchange.getRequestURI().getQuery());

    Map<String, String> queryParams = queryToMap(httpExchange.getRequestURI().getQuery());
    Set<String> keys= headers.keySet();
    Iterator<String> itr = keys.iterator();
    while(itr.hasNext())
    {
      String key = (String)itr.next();
    }

    File file = new File(ACEConstants.WEB_SERVER_CTX_ROOT + uri.getPath()).getCanonicalFile();
    String resource = uri.getPath().substring(
              uri.getPath().indexOf(ACEConstants.WEB_SERVER_CTX_ROOT)+ACEConstants.WEB_SERVER_CTX_ROOT.length()+1);

      if(httpReqMethod.equals(ACEConstants.HTTP_REQUEST_METHOD_POST) )
      {
        if(contentType != null && contentType.contains("multipart/form-data"))
        {
          if(resource.equals("fileUpload"))
          {
            inputStrm = httpExchange.getRequestBody();
            destFile = new FileOutputStream(new File("D:\\"+queryParams.get("fileName")));
            String contentLength = headers.get("Content-length").toString();
            long fileSize = (Long.parseLong(contentLength.substring(1, contentLength.length()-1)));
            int iteration = 1;
            long bytesToBeRead = (fileSize > 1024) ? ((iteration * 1024)) : (inputStrm.available());
            long bytesRemaining = (fileSize) - (iteration * 1024);
              byte[] bytes = new byte[1024];

            if(fileSize <= 1024) 
            {
              bytes = new byte[inputStrm.available()];
              inputStrm.read(bytes);
              destFile.write(bytes);
            }
            else {

              while (inputStrm.read(bytes) != -1) {
                iteration++;
                destFile.write(bytes);
                bytesRemaining =  ( fileSize - ((iteration-1) * 1024));
                if (bytesRemaining >= 1024) {
                  bytesToBeRead = 1024;
                  bytes = new byte[1024];
                }
                else {
                  bytes = new byte[inputStrm.available()];

                  inputStrm.read(bytes);
                  destFile.write(bytes);
                  break;
                }
              }
            }
            destFile.close();
          }
        } 
      }
    }

Here's the HTML code

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script type="text/javascript">

        function processForm(frm)
        {
            var fu1 = document.getElementsByName("datafile");
            var filename = fu1[0].value;
            filename = filename.substring(filename.lastIndexOf("\\")+1);
            alert("You selected " + filename);
            frm.action = "http://localhost:64444/ACE/fileUpload?fileName="+filename;
            return true;
        }

    </script>
</head>
<body>

<form name="myForm" enctype="multipart/form-data" method="post" acceptcharset="UTF-8" onsubmit="processForm(this);">
    <p>
        Please specify a file, or a set of files:<br>
        <input type="file" name="datafile" size="40">
    </p>
    <div>
        <input type="submit" value="Send">
    </div>
</form>

</body>
</html>

What is going wrong here? Help would be much appreciated.

EDIT 1:

If the input file is a text file containing text : 1234567890
The output file has contents :

 ------WebKitFormBoundaryKBRUiUWrIpW9wq2j
Content-Disposition: form-data; name="textline"


------WebKitFormBoundaryKBRUiUWrIpW9wq2j
Content-Disposition: form-data; name="datafile"; filename="test.txt"
Content-Type: text/plain

1234567890
------WebKitFormBoundaryKBRUiUWrIpW9wq2j--
arka.b
  • 204
  • 2
  • 13
Piyp791
  • 649
  • 1
  • 13
  • 28
  • Please share some errors, what get's written... something showing the error. – Jan Nov 20 '15 at 09:19
  • You asked for multipart/form-data and that's what you get there. Did you check http://stackoverflow.com/questions/2422468/how-to-upload-files-to-server-using-jsp-servlet ? – Jan Nov 20 '15 at 11:18
  • @Jan : How do I get the HTTPServletrequest through HTTPExchange? – Piyp791 Nov 20 '15 at 12:46
  • You don't. But you can still use apache commons fileupload by creating a custom wrapper - see my answer. – Jan Nov 24 '15 at 10:25
  • What error are you getting ? – reos Nov 24 '15 at 15:29

2 Answers2

10

How to Upload Files with HttpHandler

File Upload to HttpHandler results in boundary and other MIME information being written into the request contents. As parsing this information is quite comples and error-prone, one could resort to use Commons FileUpload which is proven to work great in classic Servlet environments.

Consider this example with a custom made ContextRequest. This would handle all the parsing of boundaries added into your request body by multipart/form-data while still allowing you to keep HttpHandler interface.

The main idea consists of implementing an own version of ContextRequest to work with the data provided in HttpHandler:

   public HttpHandlerRequestContext implements RequestContext {

          private HttpExchange http;

          public HttpHandlerRequestContext(HttpExchange http) {
                this.http = http;
          }

          @Override
          public String getCharacterEncoding() {
                //Need to figure this out yet
                return "UTF-8"; 
          }

          @Override
          public int getContentLength() {
                //tested to work with 0 as return. Deprecated anyways
                return 0; 
          }

          @Override
          public String getContentType() {
                //Content-Type includes the boundary needed for parsing
                return http.getRequestHeaders().getFirst("Content-type");
          }

          @Override
          public InputStream getInputStream() throws IOException {
                 //pass on input stream
                return http.getRequestBody();
          }
 }

For reference: Here's a working example listing the received files.

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map.Entry;

import org.apache.commons.fileupload.FileItem;

import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class HttpServerTest {

    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
        server.createContext("/fileupload", new MyHandler());
        server.setExecutor(null); // creates a default executor
        server.start();
    }

    static class MyHandler implements HttpHandler {
        @Override
        public void handle(final HttpExchange t) throws IOException {
            for(Entry<String, List<String>> header : t.getRequestHeaders().entrySet()) {
                System.out.println(header.getKey() + ": " + header.getValue().get(0));
            }
            DiskFileItemFactory d = new DiskFileItemFactory();      

            try {
                ServletFileUpload up = new ServletFileUpload(d);
                List<FileItem> result = up.parseRequest(new RequestContext() {

                    @Override
                    public String getCharacterEncoding() {
                        return "UTF-8";
                    }

                    @Override
                    public int getContentLength() {
                        return 0; //tested to work with 0 as return
                    }

                    @Override
                    public String getContentType() {
                        return t.getRequestHeaders().getFirst("Content-type");
                    }

                    @Override
                    public InputStream getInputStream() throws IOException {
                        return t.getRequestBody();
                    }

                });
                t.getResponseHeaders().add("Content-type", "text/plain");
                t.sendResponseHeaders(200, 0);
                OutputStream os = t.getResponseBody();               
                for(FileItem fi : result) {
                    os.write(fi.getName().getBytes());
                    os.write("\r\n".getBytes());
                    System.out.println("File-Item: " + fi.getFieldName() + " = " + fi.getName());
                }
                os.close();

            } catch (Exception e) {
                e.printStackTrace();
            }            
        }
    }
}
Jan
  • 13,738
  • 3
  • 30
  • 55
  • works like a charm. Thanks!!! Just fetched the content type from FileItem.getContentType(), and wrote the content using os.get(). – Piyp791 Nov 26 '15 at 10:18
  • This solution is not working anymore. I am getting this error `cannot access HttpServletRequest`. – Dhruv garg Nov 17 '20 at 09:04
  • Hi @Dhruvgarg, what exactly are you trying to do? I don't use HttpServletRequest in my example. You might be missing an import / a dependency to servlet-api? – Jan Nov 17 '20 at 09:14
  • 1
    Hi, I was trying to implement very simple web framework using annotations in java (for learning only) and was unable to manually implement form-data logic. I added `javax.servlet-api` and now it's working, sorry my mistake, I didn't knew servlet is being used internally. – Dhruv garg Nov 17 '20 at 09:22
  • Hi, it does not work for HTTPS, it seems caused by 2 certificates, my HttpsServer has its own certificate, and the servlet has another one, I guest. – Eyes Blue Mar 12 '22 at 14:11
-1

I have faced same issue please use Apache httpClient API for it its best api for connection with URL(file uploading and dealing with multipart form data)

mayank agrawal
  • 628
  • 5
  • 20
  • Question was about receiving end - httpClient is for sending end (where HTML is used...) – Jan Nov 25 '15 at 20:42