使用httpclient上传文件

文章介绍了在HTTP协议中上传文件时Content-Type的使用,重点讲述了HttpClient4.x版本上传文件的步骤,包括添加依赖、构建MultipartEntityBuilder以及处理各种类型的参数,如文件和普通文本。代码示例展示了如何使用HttpClient执行multipart/form-data类型的POST请求。
摘要由CSDN通过智能技术生成

HTTP协议上传文件

在 HTTP 协议中,Content-Type 头部用于指示资源的类型。一般的服务端框架,都内置了自动解析 HTTP 数据格式的功能,它们通常根据 Content-Type 来获知数据的编码格式,从而对请求体进行解析。

在客户端发送到服务端的请求中,有 4 种常见的 Content-Type 类型:

  • application/x-www-form-urlencoded:浏览表单默认的编码方式。将数据按照 k1=v1&k2=v2 格式组装,并对特殊符号进行转义。

  • multipart/form-data:上传文件时的编码方式。需要指定一个复杂的boundary(分隔符),避免和内容冲突。

  • application/json:请求体是序列化后的 json 字符串

  • text/plain:普通文本

综上:使用HTTP上传文件时,在请求头中指定 Content-Type=Content-Type: multipart/form-data; boundary=(一个复杂的分隔符)

httpclient上传文件

httpclient有多个版本,其中4.x、5.x 版本的 API 较为接近,与 3.x 版本差异较大。接下来文章会以 4.x 版本为例,介绍 httpclient 上传文件的细节。

添加相关 pom 依赖
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.1</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
    <version>4.5.12</version>
</dependency>

4.3版本后,原有的文件上传方法 MultipartEntity 已废弃,推荐使用 httpmime 下的 MultipartEntityBuilder。

httpclient 的使用方法
  1. 创建 HttpClient 对象。

  1. 创建 HttpUriRequest 对象,指定请求 url。HttpGet、HttpPost 分别用于发送 get 和 post 请求。

  1. 设置请求参数。url 中的参数,在上一步中,通过 URIBuilder.addParameter 设置;请求体中的参数,通过 HttpPost.setEntity 方法设置。

  1. 调用 httpClient.execute(httpRequestBase, context) 发起请求,返回一个 HttpResponse。

  1. 调用 httpResponse.getEntity() 获取响应体。

  1. 关闭 HttpClient 对象,释放资源。

以上步骤是使用的逻辑顺序,实际代码顺序可能会有些不同。

代码示例
public class HttpUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(HttpUtil.class);
    private static final ContentType STRING_CONTENT_TYPE = ContentType.create("text/plain", StandardCharsets.UTF_8);

    public static FacadeResponse multipartPost(String url, Map<String, String> headers, Map<String, Object> paramMap) {
        // 创建 HttpPost 对象
        HttpPost httpPost = new HttpPost(url);
        // 设置请求头
        if (MapUtils.isNotEmpty(headers)) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                httpPost.setHeader(key, value);
            }
        }
        // 设置请求参数
        if (MapUtils.isNotEmpty(paramMap)) {
            // 使用 MultipartEntityBuilder 构造请求体
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            //设置浏览器兼容模式
            builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
            //设置请求的编码格式
            builder.setCharset(Consts.UTF_8);
            // 设置 Content-Type
            builder.setContentType(ContentType.MULTIPART_FORM_DATA);
            for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                String key = entry.getKey();
                Object value = paramMap.get(key);
                // 添加请求参数
                addMultipartBody(builder, key, value);
            }
            HttpEntity entity = builder.build();
            // 将构造好的 entity 设置到 HttpPost 对象中
            httpPost.setEntity(entity);
        }
        return execute(httpPost, null);
    }

    private static void addMultipartBody(MultipartEntityBuilder builder, String key, Object value) {
        if (value == null) {
            return;
        }
        // MultipartFile 是 spring mvc 接收到的文件。
        if (value instanceof MultipartFile) {
            MultipartFile file = (MultipartFile) value;
            try {
                builder.addBinaryBody(key, file.getInputStream(), ContentType.MULTIPART_FORM_DATA, file.getOriginalFilename());
            } catch (IOException e) {
                LOGGER.error("read file err.", e);
            }
        } else if (value instanceof File) {
            File file = (File) value;
            builder.addBinaryBody(key, file, ContentType.MULTIPART_FORM_DATA, file.getName());
        } else if (value instanceof List) {
            // 列表形式的参数,要一个一个 add
            List<?> list = (List<?>) value;
            for (Object o : list) {
                addMultipartBody(builder, key, o);
            }
        } else if (value instanceof Date) {
            // 日期格式的参数,使用约定的格式
            builder.addTextBody(key, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value));
        } else {
            // 使用 UTF_8 编码的 ContentType,否则可能会有中文乱码问题
            builder.addTextBody(key, value.toString(), STRING_CONTENT_TYPE);
        }
    }

    private static FacadeResponse execute(HttpRequestBase httpRequestBase, HttpContext context) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 使用 try-with-resources 发起请求,保证请求完成后资源关闭
        try (CloseableHttpResponse httpResponse = httpClient.execute(httpRequestBase, context)) {
            // 处理响应头
            Map<String, List<String>> headers = headerToMap(httpResponse.getAllHeaders());
            // 处理响应体
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                String entityContent = EntityUtils.toString(httpEntity, Consts.UTF_8);
                return new FacadeResponse(httpRequestBase.getRequestLine().getUri(), httpResponse.getStatusLine().getStatusCode(), headers, entityContent);
            }
        } catch (Exception ex) {
            LOGGER.error("http execute failed.", ex);
        }
        return new FacadeResponse(httpRequestBase.getRequestLine().getUri(), HttpStatus.INTERNAL_SERVER_ERROR.value(), null, "http execute failed.");
    }

    /**
     * 将headers转map
     *
     * @param headers 头信息
     * @return map
     */
    private static Map<String, List<String>> headerToMap(Header[] headers) {
        if (null == headers || headers.length == 0) {
            return Collections.emptyMap();
        }
        Map<String, List<String>> map = new HashMap<>();
        for (Header header : headers) {
            map.putIfAbsent(header.getName(), Lists.newArrayList(header.getValue()));
        }
        return map;
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FacadeResponse implements Serializable {
    // url, 包含?参数
    private String url;
    // 返回状态码
    private int statusCode;
    // 返回头信息
    private Map<String, List<String>> headers;
    // 返回entity内容
    private String entityContent;
}

后记

这篇文章中的代码是笔者翻阅了许多资料后写出的。过程中遇到了 httpclient API 变更的问题,列表参数传递的问题,中文乱码的问题等等。

从上文的代码中,我们可以发现,使用 httpclient 上传文件时,代码还是相对复杂的,有许多注意点。那有没有一个简洁的 HTTP 工具类呢?

笔者后来发现了 hutool 提供的 HttpUtil,可以非常方便地上传下载文件,推荐读者们去尝试一下。

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
以下是使用HttpClient上传文件的示例代码: ``` import java.io.File; import java.io.IOException; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; public class FileUploader { public static void main(String[] args) { String url = "http://example.com/upload"; String filePath = "/path/to/file.txt"; // 创建HttpClient CloseableHttpClient httpClient = HttpClients.createDefault(); // 创建HttpPost请求 HttpPost httpPost = new HttpPost(url); // 创建multipart/form-data实体 File file = new File(filePath); HttpEntity httpEntity = MultipartEntityBuilder.create() .addBinaryBody("file", file, ContentType.DEFAULT_BINARY, file.getName()) .build(); // 设置请求实体 httpPost.setEntity(httpEntity); try { // 执行请求 HttpResponse response = httpClient.execute(httpPost); // 输出响应结果 System.out.println(response.getStatusLine().getStatusCode()); System.out.println(response.getEntity().getContent().toString()); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭HttpClient try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } } ``` 在上面的示例代码中,我们使用了Apache HttpClient 4.x库来发送POST请求,并上传文件。我们首先创建了一个CloseableHttpClient实例,然后创建一个HttpPost实例,并设置请求实体为multipart/form-data类型的实体。 在实体中,我们添加了一个二进制文件体,它将文件添加到请求中。在这个例子中,我们使用了File类来表示文件,并用ContentType.DEFAULT_BINARY创建了一个ContentType实例。 最后,我们执行请求,并输出响应结果。注意,在使用HttpClient时,我们需要手动关闭HttpClient实例,以释放连接和资源。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值