从Java客户端上传文件到HTTP服务器

82

我想要将一些文件上传到HTTP服务器上。基本上我需要的是向服务器发出POST请求,带上一些参数和文件。我看过一些仅上传文件的例子,但是没有找到如何传递额外参数的方法。

有没有最简单和免费的解决方案?是否有任何上传文件的示例可以供我学习?我已经谷歌了几个小时,但是(也许只是那种日子)没有找到我需要的东西。最好的解决方案是不涉及任何第三方类或库。

8个回答

112

通常情况下,您会使用java.net.URLConnection发送HTTP请求。对于混合POST内容(二进制和字符数据),通常也会使用multipart/form-data编码。点击链接以获取有关如何组成multipart/form-data请求正文的信息和示例。该规范在RFC2388中进行了更详细的描述。

这是一个入门示例:

String url = "http://example.com/upload";
String charset = "UTF-8";
String param = "value";
File textFile = new File("/path/to/file.txt");
File binaryFile = new File("/path/to/file.bin");
String boundary = Long.toHexString(System.currentTimeMillis()); // Just generate some unique random value.
String CRLF = "\r\n"; // Line separator required by multipart/form-data.

URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

try (
    OutputStream output = connection.getOutputStream();
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, charset), true);
) {
    // Send normal param.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"param\"").append(CRLF);
    writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF);
    writer.append(CRLF).append(param).append(CRLF).flush();

    // Send text file.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"textFile\"; filename=\"" + textFile.getName() + "\"").append(CRLF);
    writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF); // Text file itself must be saved in this charset!
    writer.append(CRLF).flush();
    Files.copy(textFile.toPath(), output);
    output.flush(); // Important before continuing with writer!
    writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.

    // Send binary file.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"binaryFile\"; filename=\"" + binaryFile.getName() + "\"").append(CRLF);
    writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(binaryFile.getName())).append(CRLF);
    writer.append("Content-Transfer-Encoding: binary").append(CRLF);
    writer.append(CRLF).flush();
    Files.copy(binaryFile.toPath(), output);
    output.flush(); // Important before continuing with writer!
    writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.

    // End of multipart/form-data.
    writer.append("--" + boundary + "--").append(CRLF).flush();
}

// Request is lazily fired whenever you need to obtain information about response.
int responseCode = ((HttpURLConnection) connection).getResponseCode();
System.out.println(responseCode); // Should be 200

使用第三方库比如Apache Commons HttpComponents Client可以使这段代码更加简洁。

有些人在这里错误地建议使用Apache Commons FileUpload,但实际上它只适用于服务器端,客户端不能使用也不需要使用。

另请参阅


当我使用这段代码上传文本文件时,它可以正确上传,但是在上传图像文件时出现问题。我已经将内容类型更改为image/jpeg,例如:如果我上传一个6kb的图像,在服务器上显示为10kb,并且尝试查看该图像时什么也没有显示。请建议我需要进行哪些更改。 - vips
@vips:将其写成字节流而不是字符流。另请参阅https://dev59.com/vHE85IYBdhLWcg3wZyqt#2793153 - BalusC
Balus,你的示例从未使用catch块!每个异常都必须被捕获或抛出!... 此外,我对基于URL的Java事物还很陌生,但是嵌套的try块是否需要?在我的想法中,这似乎有点混乱,但如果实际上出于某种奇怪的原因需要它,请随意纠正我:P 我习惯于运行一个会引发异常的东西,在try中捕获其错误,然后关闭try-catch并继续进行...也许只是风格上的差异。 :) - Drifter64
1
@Drifter64:嗯,在方法中添加throws IOException,如public void foo() throws IOException。到目前为止,代码中没有明智的处理异常的方式,因此调用者负责处理异常。另请参阅Oracle关于Java异常的基本教程。在这种特殊情况下,嵌套的try块绝对是必需的,因为外部资源必须在使用后得到释放,否则您的应用程序最终会让运行时环境耗尽资源而崩溃。这绝不是一个风格问题。这只是以正确的方式编写代码。 - BalusC
1
太好了,它起作用了!现在的问题是我想获取从服务器返回的响应。我该如何捕获它? :) 谢谢! - Disapamok
显示剩余8条评论

45

以下是如何使用Apache HttpClient执行操作的方法(适用于不介意使用第三方库的人):

    HttpEntity entity = MultipartEntityBuilder.create()
                       .addPart("file", new FileBody(file))
                       .build();

    HttpPost request = new HttpPost(url);
    request.setEntity(entity);

    HttpClient client = HttpClientBuilder.create().build();
    HttpResponse response = client.execute(request);

16
MultipartEntity已经被弃用,我们需要使用MultipartBuilder来代替:HttpEntity entity = MultipartEntityBuilder.create().addTextBody("field1", "value1").addBinaryBody("myfile", new File("/path/file1.txt"), ContentType.create("application/octet-stream"), "file1.txt").build(); - encrest
1
您还需要像这样替换已弃用的DefaultHttpClient:HttpClient client = HttpClientBuilder.create().build(); - mmaceachran
Emmanuel - 我仍然从端点收到400个错误请求。您能否请检查 https://stackoverflow.com/questions/70543352/how-to-send-binary-file-using-httpclient-approach? - ezhil

6

点击链接获取示例:使用Apache HttpComponents在Java中上传文件

http://hc.apache.org/httpcomponents-client-ga/httpmime/examples/org/apache/http/examples/entity/mime/ClientMultipartFormPost.java

下载相关库:

https://hc.apache.org/downloads.cgi

我在我的代码中使用的是4.5.3.zip版本,可以正常工作。

以下是我的可工作代码:

import java.io.File;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class ClientMultipartFormPost {

     public static void main(String[] args) throws Exception {

          CloseableHttpClient httpclient = HttpClients.createDefault();
          try {
             HttpPost httppost = new HttpPost("http://localhost:8080/MyWebSite1/UploadDownloadFileServlet");

             FileBody bin = new FileBody(new File("E:\\meter.jpg"));
             StringBody comment = new StringBody("A binary file of some kind", ContentType.TEXT_PLAIN);

             HttpEntity reqEntity = MultipartEntityBuilder.create()
                .addPart("bin", bin)
                .addPart("comment", comment)
                .build();


             httppost.setEntity(reqEntity);

             System.out.println("executing request " + httppost.getRequestLine());
             CloseableHttpResponse response = httpclient.execute(httppost);
           try {
                System.out.println("----------------------------------------");
                System.out.println(response.getStatusLine());
                HttpEntity resEntity = response.getEntity();
                if (resEntity != null) {
                     System.out.println("Response content length: " +    resEntity.getContentLength());
                }
              EntityUtils.consume(resEntity);
             } finally {
                 response.close();
            }
       } finally {
          httpclient.close();
      }
   }

}

Prashant - 我仍然从端点收到400个错误请求。你能否请检查一下 https://stackoverflow.com/questions/70543352/how-to-send-binary-file-using-httpclient-approach ? - ezhil

2

以下是使用Java 11的java.net.http包实现的方法:

    var fileA = new File("a.pdf");
    var fileB = new File("b.pdf");

    var mimeMultipartData = MimeMultipartData.newBuilder()
            .withCharset(StandardCharsets.UTF_8)
            .addFile("file1", fileA.toPath(), Files.probeContentType(fileA.toPath()))
            .addFile("file2", fileB.toPath(), Files.probeContentType(fileB.toPath()))
            .build();

    var request = HttpRequest.newBuilder()
            .header("Content-Type", mimeMultipartData.getContentType())
            .POST(mimeMultipartData.getBodyPublisher())
            .uri(URI.create("http://somehost/upload"))
            .build();

    var httpClient = HttpClient.newBuilder().build();
    var response = httpClient.send(request, BodyHandlers.ofString());

使用以下MimeMultipartData:

public class MimeMultipartData {

    public static class Builder {

        private String boundary;
        private Charset charset = StandardCharsets.UTF_8;
        private List<MimedFile> files = new ArrayList<MimedFile>();
        private Map<String, String> texts = new LinkedHashMap<>();

        private Builder() {
            this.boundary = new BigInteger(128, new Random()).toString();
        }

        public Builder withCharset(Charset charset) {
            this.charset = charset;
            return this;
        }

        public Builder withBoundary(String boundary) {
            this.boundary = boundary;
            return this;
        }

        public Builder addFile(String name, Path path, String mimeType) {
            this.files.add(new MimedFile(name, path, mimeType));
            return this;
        }

        public Builder addText(String name, String text) {
            texts.put(name, text);
            return this;
        }

        public MimeMultipartData build() throws IOException {
            MimeMultipartData mimeMultipartData = new MimeMultipartData();
            mimeMultipartData.boundary = boundary;

            var newline = "\r\n".getBytes(charset);
            var byteArrayOutputStream = new ByteArrayOutputStream();
            for (var f : files) {
                byteArrayOutputStream.write(("--" + boundary).getBytes(charset)); 
                byteArrayOutputStream.write(newline);
                byteArrayOutputStream.write(("Content-Disposition: form-data; name=\"" + f.name + "\"; filename=\"" + f.path.getFileName() + "\"").getBytes(charset));
                byteArrayOutputStream.write(newline);
                byteArrayOutputStream.write(("Content-Type: " + f.mimeType).getBytes(charset));
                byteArrayOutputStream.write(newline);
                byteArrayOutputStream.write(newline);
                byteArrayOutputStream.write(Files.readAllBytes(f.path));
                byteArrayOutputStream.write(newline);
            }
            for (var entry: texts.entrySet()) {
                byteArrayOutputStream.write(("--" + boundary).getBytes(charset));
                byteArrayOutputStream.write(newline);
                byteArrayOutputStream.write(("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"").getBytes(charset));
                byteArrayOutputStream.write(newline);
                byteArrayOutputStream.write(newline);
                byteArrayOutputStream.write(entry.getValue().getBytes(charset));
                byteArrayOutputStream.write(newline);
            }
            byteArrayOutputStream.write(("--" + boundary + "--").getBytes(charset));

            mimeMultipartData.bodyPublisher = BodyPublishers.ofByteArray(byteArrayOutputStream.toByteArray());
            return mimeMultipartData;
        }

        public class MimedFile {

            public final String name;
            public final Path path;
            public final String mimeType;

            public MimedFile(String name, Path path, String mimeType) {
                this.name = name;
                this.path = path;
                this.mimeType = mimeType;
            }
        }
    }

    private String boundary;
    private BodyPublisher bodyPublisher;

    private MimeMultipartData() {
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    public BodyPublisher getBodyPublisher() throws IOException {
        return bodyPublisher;
    }

    public String getContentType() {
        return "multipart/form-data; boundary=" + boundary;
    }

}

0
public static String simSearchByImgURL(int  catid ,String imgurl) throws IOException{
    CloseableHttpClient httpClient = HttpClients.createDefault();
    CloseableHttpResponse response = null;
    String result =null;
    try {
        HttpPost httppost = new HttpPost("http://api0.visualsearchapi.com:8084/vsearchtech/api/v1.0/apisim_search");
        StringBody catidBody = new StringBody(catid+"" , ContentType.TEXT_PLAIN);
        StringBody keyBody = new StringBody(APPKEY , ContentType.TEXT_PLAIN);
        StringBody langBody = new StringBody(LANG , ContentType.TEXT_PLAIN);
        StringBody fmtBody = new StringBody(FMT , ContentType.TEXT_PLAIN);
        StringBody imgurlBody = new StringBody(imgurl , ContentType.TEXT_PLAIN);
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.addPart("apikey", keyBody).addPart("catid", catidBody)
        .addPart("lang", langBody)
        .addPart("fmt", fmtBody)
        .addPart("imgurl", imgurlBody);
        HttpEntity reqEntity =  builder.build();
        httppost.setEntity(reqEntity);
        response = httpClient.execute(httppost);
        HttpEntity resEntity = response.getEntity();
        if (resEntity != null) {
           // result = ConvertStreamToString(resEntity.getContent(), "UTF-8");
            String charset = "UTF-8";   
          String content=EntityUtils.toString(response.getEntity(), charset);   
            System.out.println(content);
        }
        EntityUtils.consume(resEntity);
    }catch(Exception e){
        e.printStackTrace();
    }finally {
        response.close();
        httpClient.close();
    }
    return result;
}

尝试上面的示例。它来自这个网站http://www.visualsearchapi.com。完整的代码链接在这里http://www.visualsearchapi.com/apiDoc/apiDoc?apiDoc=V。祝你好运。 - deeplinks

0

我建议使用Apache http类而不是Vanilla Java。这是一个与Java 8兼容的简单解决方案:

// Create http client.

CloseableHttpClient httpClient = HttpClients.createDefault();
final File file = new File("<FILE PATH TO POST>");

// Specify the content type of the attached file.

FileBody filebody = new FileBody(file, ContentType.MULTIPART_FORM_DATA);
MultipartEntityBuilder entitybuilder = MultipartEntityBuilder.create();
entitybuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);

// Add the binary file to the entity we send later on.

entitybuilder.addBinaryBody("file", file);
HttpEntity mutiPartHttpEntity = entitybuilder.build();
RequestBuilder reqbuilder = RequestBuilder.post("URL TO POST DATA WITH ALL THE PARAMETERS");
reqbuilder.setEntity(mutiPartHttpEntity);
HttpUriRequest multipartRequest = reqbuilder.build();

// Using the http client, execute http post. Variable httpresponse would contain the reply back explaining http response code, and ...

HttpResponse httpresponse = httpClient.execute(multipartRequest);
httpClient.close();

为了将参数嵌入到POST消息中,您可以将它们简单地嵌入到用于发布数据的URL中。使用此规则:?=&=。例如: https://company.com/jobs/status?jobId=876&apiKey=123 要找出与使用Http post发布的文件适合的数据类型,请参阅此链接:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types 导入org.apache.http.*包
如果导入未起作用,则可能需要更改pom.xml文件(如果您正在使用Maven),并将Apache软件包添加到此xml文件中。请查看此处

-1
protected void doPost(HttpServletRequest request,
        HttpServletResponse response) throws ServletException, IOException {

    boolean isMultipart = ServletFileUpload.isMultipartContent(request);

    if (!isMultipart) {
        return;
    }

    DiskFileItemFactory factory = new DiskFileItemFactory();

    factory.setSizeThreshold(MAX_MEMORY_SIZE);

    factory.setRepository(new File(System.getProperty("java.io.tmpdir")));

    String uploadFolder = getServletContext().getRealPath("")
            + File.separator + DATA_DIRECTORY;//DATA_DIRECTORY is directory where you upload this file on the server

    ServletFileUpload upload = new ServletFileUpload(factory);

    upload.setSizeMax(MAX_REQUEST_SIZE);//MAX_REQUEST_SIZE is the size which size you prefer

在 HTML 中使用 <form enctype="multipart/form-data"><input type="file">


这是针对服务器端(servlet)的。OP正在寻找Java客户端。 - Stefan Reich
1
没有理由因为它没有直接回答 OP 的问题就给它点踩。大多数来这里的人都是从糟糕的谷歌搜索中来的,他们会发现像这样的答案很有帮助。 - Kinjal Dixit

-7

这取决于您的框架。 (对于每个框架都可能存在更简单的解决方案)。

但是为了回答您的问题:有很多外部库提供此功能。请查看这里如何使用 apache commons fileupload。


2
我没有点踩,但我猜测原因和其他回答被踩的原因相同。你回答了如何使用servlets接收上传文件,而问题是如何发送文件。特别地,在POST中还有附加数据。一般来说,如果你能从链接页面中转述或复制出有用/相关的部分,这将使答案对读者更有用,并在链接失效时保留其有用性。 - Arkaine55

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接