如何使用Java将多部分/表单数据发布上传到Nexus存储库?

如何使用Java将多部分/表单数据发布上传到Nexus存储库?,java,http,post,nexus,apache-commons-httpclient,Java,Http,Post,Nexus,Apache Commons Httpclient,我的目标是通过Java将文件上传到Nexus。如果您不熟悉NexusRESTAPI,我需要使用多部分/表单数据内容发布帖子,以便发送Nexus字段(如我的文件所属目录)和我的文件内容 简单的解决方案是使用Apache组件工具集中的MultipartEntityBuilder,但由于项目限制,我无法访问Apache HttpClient Mime库(尽管我可以访问所有核心HTTP内容)。所以我必须做些别的事情。同样由于项目限制,我只能访问内置Java库和大多数Apache组件工具集,但我没有任何其

我的目标是通过Java将文件上传到Nexus。如果您不熟悉NexusRESTAPI,我需要使用多部分/表单数据内容发布帖子,以便发送Nexus字段(如我的文件所属目录)和我的文件内容

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

我的代码没有中断,但我能从Nexus得到的只是HTTP错误代码。我可以从Nexus获得响应头,但我无法获得Nexus接受的配置。我尝试过在Firefox的网络控制台工具中手动发布,它可以正常工作并上传一个文件(只要我有一个有效的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));
或:

我得到了一个
422:Unprocessable Entity
错误,Nexus没有提供关于错误格式的其他信息。响应主体为空

我想可能是
setEntity
为我添加了一个内容类型multipart/form数据,因为我在标题中添加了另一个,所以很混乱。所以我试着:

post.setEntity(new StringEntity(formBody);
post.addHeader(new BasicHeader("Accept", "application/json");
这给了我
415:不支持的媒体类型
。再次没有回应机构。我有点期待这种情况,因为Nexus服务器拒绝您发布的任何内容,除非是多部分/表单数据

因此,接下来我尝试让EntityBuilder生成标题:

post.setEntity(EntityBuilder.create().setText(formBody).
               setContentType(ContentType.MULTIPART_FORM_DATA).build());
post.addHeader(new BasicHeader("Accept", "application/json"));
但是我得到了
500:internalserver Error
,响应头说:

java.io.IOException:无法获取多部分的边界

好吧,至少这很简单。显然,我必须给它一个界限。但是这样做会给我上面提到的
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类型不能包含保留字符

好的,那么,我尝试在没有多部分过程的情况下,将边界本身添加到标题中(我没有这样做,只是为了工作,因为必须一起指定它们):

正如预期的那样,这没有起作用。令人惊讶的是,我得到了一个实际的html页面作为我的响应主体,但它只是说
400:Bad response

好吧,我说“去他妈的”。然后,我尝试使用Java的HTTP内容,并创建一个多部分编写器来手工编写

我找到了一个用户的答案,我基本上是逐字复制来测试的。我将把下面的代码留给后代,但我自己并没有想出这个解决方案

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;
    }
}
编辑:我已经通过Python脚本很好地实现了这一点,所以我知道这不可能是服务器问题。唯一的问题是我必须将这个上传步骤作为现有Java程序的一部分进行集成,所以我不能只制作一个两行Python脚本

因此,现在我恳求Stack Overflow社区看看是否有人能提供帮助

post.setEntity(new StringEntity(formBody);
post.addHeader(new BasicHeader("Accept", "application/json");
post.addHeader(new BasicHeader("Content-Type", "multipart/form-data; boundary=" + generatedBoundary));
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;
    }
}
--$$$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$$$--