我的目标是通过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社区看看是否有人可以提供帮助。