您如何进行多部分/表单数据POST,以使用Java将文件上传到Nexus存储库?

时间:2019-07-17 23:53:54

标签: java http post nexus apache-commons-httpclient

我的目标是通过Java将文件上传到Nexus。如果您不熟悉Nexus REST API,则我需要对多部分/表单数据内容进行POST,以便发送Nexus字段(例如我的文件所属的目录)和我的文件内容。

简单的解决方案是使用Apache Components工具集中的MultipartEntityBuilder,但是我没有可以访问Apache HttpClient Mime库(尽管我可以访问所有核心HTTP东西),由于项目限制,无法访问它。所以我必须做其他事情。同样,由于项目限制,我只能访问内置的Java库和大多数Apache Components工具集,但没有任何其他HTTP库(例如okhttp,Jersey,Restlet等)。 / p>

我的代码没有中断,但是我从Nexus可以得到的只是HTTP错误代码。我可以从Nexus获得响应标头,但无法获得Nexus接受的配置。我已经尝试过在Firefox的网络控制台工具中手动执行POST,并且它可以正常工作并在那里上传文件(只要我有用于身份验证的有效Cookie)即可。因此,我知道格式错误的不是实际正文,因为如果手动进行操作,它会起作用。问题是我的HttpClient设置中添加或配置错误,因此Nexus无法理解我的意思。

到目前为止,我有:

CloseableHttpClient clientConnection = ConnectionCommon.getCloseableHttpClient(ssl);
/*
 * getCloseableClient is a custom function which returns a client with
 * proper SSL configuration set up depending on what's needed.
 */
HttpPost post = new HttpPost(ConnectionCommon.constructURL(schema, host, portNum, dataType, repo));

String formBody = getFormBody();

/* Create the body entity.
 * I've tried various version of this, and none have worked.  I wanted to try
 * creating my own ContentType but I couldn't due to it being a final class.
 */
post.setEntity(new StringEntity(formBody));
// post.setEntity(new EntityBuilder.create().setText(formBody).build());

post.addHeader(new BasicHeader("Accept", "application/json"));
post.addHeader(new BasicHeader("Content-Type", "multipart/form-data; boundary=" + generatedBoundary));

// Get the server response, after generating our authentication context (if needed).
CloseableHttpResponse serverResponse = getCloseableResponse(clientConnection, post, auth);

// Process output and stuff here...

如果我这样做:

post.setEntity(new StringEntity(formBody));
post.addHeader(new BasicHeader("Accept", "application/json"));
post.addHeader(new BasicHeader("Content-Type", "multipart/form-data; boundary=" + generatedBoundary));

或:

post.setEntity(EntityBuilder.create().setText(formBody).
               setContentType(ContentType.MULTIPART_FORM_DATA).build());
post.addHeader(new BasicHeader("Accept", "application/json"));
post.addHeader(new BasicHeader("Content-Type", "multipart/form-data; boundary=" + generatedBoundary));

我收到422: Unprocessable Entity错误,但Nexus没有提供其他有关格式错误的信息。响应正文为空。

我以为setEntity可能正在为我添加Content-Type multipart / form-data,并且因为我在标头中添加了另一个,所以很困惑。所以我尝试了:

post.setEntity(new StringEntity(formBody);
post.addHeader(new BasicHeader("Accept", "application/json");

哪个给了我415: Unsupported Media Type。再次没有反应的身体。我有点期望,因为Nexus服务器会拒绝您发布的所有内容,除非它是multipart / form-data。

因此,接下来我尝试让EntityBuilder设置标题:

post.setEntity(EntityBuilder.create().setText(formBody).
               setContentType(ContentType.MULTIPART_FORM_DATA).build());
post.addHeader(new BasicHeader("Accept", "application/json"));

但是随后我得到500: Internal Server Error,响应标头说:

java.io.IOException: Unable to get boundary for multipart

好吧,至少这很简单。显然,我必须给它一个界限。但是这样做就给了我上面提到的422

好吧,让我将边界作为EntityBuilder的MIME的一部分传递出去:

post.setEntity(EntityBuilder.create().setText(formBody).
               setContentType(ContentType.create("multipart/form-data; boundary=" + generatedBoundary, Consts.UTF_8)).build());
post.addHeader(new BasicHeader("Accept", "application/json"));

但这甚至没有运行,因为事实证明,“;”是要传递到ContentType的“无效”字符。

java.lang.IllegalArgumentException: MIME type may not contain reserved characters

好吧,所以,我尝试然后在标头中自行添加边界,而没有多部分处理(因为您必须一起指定它们,所以我没有其他工作):

post.setEntity(new StringEntity(formBody);
post.addHeader(new BasicHeader("Accept", "application/json");
post.addHeader(new BasicHeader("Content-Type", "multipart/form-data; boundary=" + generatedBoundary));

按预期,这没有用。令人惊讶的是,我得到了一个实际的html页面作为响应正文,但它只是说400: Bad Response

好的,所以,我说。然后,我去尝试使用Java的HTTP东西,并创建了一个Multipart编写器来手工完成。

我找到了用户here的答案KDeogharkar,基本上是逐字逐句地进行了测试。为了后代,我将代码放在下面,但是我自己并未提出此解决方案。

public class MultipartUtility {
    private final String boundary;
    private static final String LINE_FEED = "\r\n";
    private HttpURLConnection httpConn;
    private String charset;
    private OutputStream outputStream;
    private PrintWriter writer;

    /**
     * This constructor initializes a new HTTP POST request with content type
     * is set to multipart/form-data
     *
     * @param requestURL
     * @param charset
     * @throws IOException
     */
    public MultipartUtility(String requestURL, String charset)
            throws IOException {
        this.charset = charset;

        // creates a unique boundary based on time stamp
        boundary = "===" + System.currentTimeMillis() + "===";
        URL url = new URL(requestURL);
        httpConn = (HttpURLConnection) url.openConnection();
        httpConn.setUseCaches(false);
        httpConn.setDoOutput(true);    // indicates POST method
        httpConn.setDoInput(true);
        httpConn.setRequestProperty("Content-Type",
                "multipart/form-data; boundary=" + boundary);
        outputStream = httpConn.getOutputStream();
        writer = new PrintWriter(new OutputStreamWriter(outputStream, charset),
                true);
    }

    /**
     * Adds a form field to the request
     *
     * @param name  field name
     * @param value field value
     */
    public void addFormField(String name, String value) {
        writer.append("--" + boundary).append(LINE_FEED);
        writer.append("Content-Disposition: form-data; name=\"" + name + "\"")
                .append(LINE_FEED);
        writer.append("Content-Type: text/plain; charset=" + charset).append(
                LINE_FEED);
        writer.append(LINE_FEED);
        writer.append(value).append(LINE_FEED);
        writer.flush();
    }

    /**
     * Adds a upload file section to the request
     *
     * @param fieldName  name attribute in <input type="file" name="..." />
     * @param uploadFile a File to be uploaded
     * @throws IOException
     */
    public void addFilePart(String fieldName, File uploadFile)
            throws IOException {
        String fileName = uploadFile.getName();
        writer.append("--" + boundary).append(LINE_FEED);
        writer.append(
                "Content-Disposition: form-data; name=\"" + fieldName
                        + "\"; filename=\"" + fileName + "\"")
                .append(LINE_FEED);
        writer.append(
                "Content-Type: "
                        + URLConnection.guessContentTypeFromName(fileName))
                .append(LINE_FEED);
        writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED);
        writer.append(LINE_FEED);
        writer.flush();

        FileInputStream inputStream = new FileInputStream(uploadFile);
        byte[] buffer = new byte[4096];
        int bytesRead = -1;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        outputStream.flush();
        inputStream.close();
        writer.append(LINE_FEED);
        writer.flush();
    }

    /**
     * Adds a header field to the request.
     *
     * @param name  - name of the header field
     * @param value - value of the header field
     */
    public void addHeaderField(String name, String value) {
        writer.append(name + ": " + value).append(LINE_FEED);
        writer.flush();
    }

    /**
     * Completes the request and receives response from the server.
     *
     * @return a list of Strings as response in case the server returned
     * status OK, otherwise an exception is thrown.
     * @throws IOException
     */
    public List<String> finish() throws IOException {
        List<String> response = new ArrayList<String>();
        writer.append(LINE_FEED).flush();
        writer.append("--" + boundary + "--").append(LINE_FEED);
        writer.close();

        // checks server's status code first
        int status = httpConn.getResponseCode();
        if (status == HttpURLConnection.HTTP_OK) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    httpConn.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                response.add(line);
            }
            reader.close();
            httpConn.disconnect();
        } else {
            throw new IOException("Server returned non-OK status: " + status);
        }
        return response;
    }
}

这似乎写的很好,但我什至无法以这种方式从服务器获得响应,而且我的InputStreams都为空。我确认它没有将文件上传到Nexus存储库中,所以我不知道为什么它不起作用。

为了清楚起见,这是我的表单数据(我也尝试了各种边界,只是为了确保所使用的字符没有问题):

--$$$010678954$$$
Content-Disposition: form-data; name="raw.directory"

PleaseWork
--$$$010678954$$$
Content-Disposition: form-data; name="raw.asset1"; filename="please.txt"
Content-Type: text/plain

Test test test
Foobar test
Random file contents blah
--$$$010678954$$$
Content-Disposition: form-data; name="raw.asset1.filename"

PleaseWorkPlease.txt
--$$$010678954$$$--

编辑:我已经通过Python脚本很好地完成了这项工作,因此我知道这不可能是服务器问题。唯一的问题是我必须将此上载步骤集成为现有Java程序的一部分,因此我不能只编写两行Python脚本。

所以现在我恳求Stack Overflow社区看看是否有人可以提供帮助。

0 个答案:

没有答案