HttpURLConnection OOM问题记录

使用HttpURLConnection 上传大文件,会出现内存溢出问题:

观察HttpURLConnection 源码:

@Overridepublic synchronized OutputStream getOutputStream() throws IOException {
    connecting = true;
    SocketPermission p = URLtoSocketPermission(this.url);

    if (p != null) {
        try {
            return AccessController.doPrivilegedWithCombiner(
                new PrivilegedExceptionAction<>() {
                    public OutputStream run() throws IOException {
                        return getOutputStream0();
                    }
                }, null, p            );
        } catch (PrivilegedActionException e) {
            throw (IOException) e.getException();
        }
    } else {
        return getOutputStream0();
    }
}

private synchronized OutputStream getOutputStream0() throws IOException {
    try {
        if (!doOutput) {
            throw new ProtocolException("cannot write to a URLConnection"                           + " if doOutput=false - call setDoOutput(true)");
        }

        if (method.equals("GET")) {
            method = "POST"; // Backward compatibility        }
        if ("TRACE".equals(method) && "http".equals(url.getProtocol())) {
            throw new ProtocolException("HTTP method TRACE" +
                                        " doesn't support output");
        }

        // if there's already an input stream open, throw an exception        if (inputStream != null) {
            throw new ProtocolException("Cannot write output after reading input.");
        }

        if (!checkReuseConnection())
            connect();

        boolean expectContinue = false;
        String expects = requests.findValue("Expect");
        if ("100-Continue".equalsIgnoreCase(expects) && streaming()) {
            http.setIgnoreContinue(false);
            expectContinue = true;
        }

        if (streaming() && strOutputStream == null) {
            writeRequests();
        }

        if (expectContinue) {
            expect100Continue();
        }
        ps = (PrintStream)http.getOutputStream();
        if (streaming()) {
            if (strOutputStream == null) {
                if (chunkLength != -1) { /* chunked */                     strOutputStream = new StreamingOutputStream(
                           new ChunkedOutputStream(ps, chunkLength), -1L);
                } else { /* must be fixed content length */                    long length = 0L;
                    if (fixedContentLengthLong != -1) {
                        length = fixedContentLengthLong;
                    } else if (fixedContentLength != -1) {
                        length = fixedContentLength;
                    }
                    strOutputStream = new StreamingOutputStream(ps, length);
                }
            }
            return strOutputStream;
        } else {
            if (poster == null) {
                poster = new PosterOutputStream();
            }
            return poster;
        }
    } catch (RuntimeException e) {
        disconnectInternal();
        throw e;
    } catch (ProtocolException e) {
        // Save the response code which may have been set while enforcing        // the 100-continue. disconnectInternal() forces it to -1        int i = responseCode;
        disconnectInternal();
        responseCode = i;
        throw e;
    } catch (IOException e) {
        disconnectInternal();
        throw e;
    }
}

public boolean streaming () {
    return (fixedContentLength != -1) || (fixedContentLengthLong != -1) ||
           (chunkLength != -1);
}

如上, 默认设置情况下streaming ()  为false。

package sun.net.www.http
public class PosterOutputStream extends ByteArrayOutputStream {
}

PosterOutputStream  默认为 ByteArrayOutputStream  子类

解决办法:

目标服务支持情况下,可以不使用HttpURLConnection的ByteArrayOutputStream缓存机制,直接将流提交到服务器上。如下函数设置:

httpConnection.setChunkedStreamingMode(0); // 或者设置自定义大小,0默认大小

    public void setChunkedStreamingMode (int chunklen) {
        if (connected) {
            throw new IllegalStateException ("Can't set streaming mode: already connected");
        }
        if (fixedContentLength != -1 || fixedContentLengthLong != -1) {
            throw new IllegalStateException ("Fixed length streaming mode set");
        }
        chunkLength = chunklen <=0? DEFAULT_CHUNK_SIZE : chunklen;
    }

遗憾的是,我上传的服务不支持这种模式。因此采用固定大小。

HttpURLConnection con = (HttpURLConnection)new URL("url").openConnection();
con.setFixedLengthStreamingMode(输出流的固定长度);
  /**
     * This method is used to enable streaming of a HTTP request body
     * without internal buffering, when the content length is known in
     * advance.
     * <p>
     * An exception will be thrown if the application
     * attempts to write more data than the indicated
     * content-length, or if the application closes the OutputStream
     * before writing the indicated amount.
     * <p>
     * When output streaming is enabled, authentication
     * and redirection cannot be handled automatically.
     * A HttpRetryException will be thrown when reading
     * the response if authentication or redirection are required.
     * This exception can be queried for the details of the error.
     * <p>
     * This method must be called before the URLConnection is connected.
     * <p>
     * <B>NOTE:</B> {@link #setFixedLengthStreamingMode(long)} is recommended
     * instead of this method as it allows larger content lengths to be set.
     *
     * @param   contentLength The number of bytes which will be written
     *          to the OutputStream.
     *
     * @throws  IllegalStateException if URLConnection is already connected
     *          or if a different streaming mode is already enabled.
     *
     * @throws  IllegalArgumentException if a content length less than
     *          zero is specified.
     *
     * @see     #setChunkedStreamingMode(int)
     * @since 1.5
     */
    public void setFixedLengthStreamingMode (int contentLength) {
        if (connected) {
            throw new IllegalStateException ("Already connected");
        }
        if (chunkLength != -1) {
            throw new IllegalStateException ("Chunked encoding streaming mode set");
        }
        if (contentLength < 0) {
            throw new IllegalArgumentException ("invalid content length");
        }
        fixedContentLength = contentLength;
    }

我是上传文件场景: 使用文件的大小作为长度

FileInputStream fileInputStream = new FileInputStream(uploadFileName);
long totalLength= fileInputStream.getChannel().size();
var boundary = "someboundary";
var temUploadUrl = "url path";
//
var url = new URL(temUploadUrl);
var connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("PUT");
connection.setRequestProperty("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE + "; boundary=" + boundary);

connection.setDoOutput(true);
// 设置 Content-Length
connection.setRequestProperty("Content-Length", String.valueOf(totalLength)); 
connection.setFixedLengthStreamingMode(totalLength);

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HttpUrlConnectionJava 标准库中用于发送 HTTP 请求和处理 HTTP 响应的类。它提供了一种简单的方式来与远程服务器进行通信。您可以使用 HttpUrlConnection 类来建立连接、发送请求、读取响应和处理错误。 以下是使用 HttpUrlConnection 发送 GET 请求的示例代码: ```java import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; public class HttpUrlConnectionExample { public static void main(String[] args) { try { URL url = new URL("http://example.com/api"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); int responseCode = connection.getResponseCode(); System.out.println("Response Code: " + responseCode); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); String line; StringBuilder response = new StringBuilder(); while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); System.out.println("Response: " + response.toString()); connection.disconnect(); } catch (Exception e) { e.printStackTrace(); } } } ``` 这个示例中,我们创建了一个 URL 对象来指定要发送请求的目标 URL。然后,我们打开一个 HttpURLConnection 连接并设置请求方法为 GET。发送请求后,我们可以获取响应码、读取响应内容,并在最后关闭连接。 您可以根据需要设置请求头、添加请求参数等。同时,HttpUrlConnection 也支持其他的 HTTP 方法,如 POST、PUT、DELETE 等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值