0
点赞
收藏
分享

微信扫一扫

HttpURLConnection OOM问题记录

猫er聆听没落的旋律 2023-12-08 阅读 49

使用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默认大小

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

HttpURLConnection con = (HttpURLConnection)new URL("url").openConnection();
con.setFixedLengthStreamingMode(输出流的固定长度);

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

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);
举报

相关推荐

0 条评论