I am trying to call Docusign REST API as is outlined in the "Step 3: Send signature request on behalf of User 2" Section in this link. I get the following error below. What is the boundary supposed set to? How do I correctly set it?
{
"errorCode": "INVALID_MULTI_PART_REQUEST",
"message": "An error was found while parsing the multipart request. Boundary terminator '--BOUNDARY; charset=utf-8--' was not found in the request."
}
public static string HttpRequest(string url, List<CELPHttpHeader> headerList, EnvelopeDefinition envelopeDefination)
{
string responseString = string.Empty;
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("accept", "application/json");
MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("multipart/form-data");
NameValueHeaderValue item = new NameValueHeaderValue("boundary", "BOUNDARY");
mediaType.Parameters.Add(item);
JsonMediaTypeFormatter formatter = new JsonMediaTypeFormatter();
HttpRequestMessage requestMessage = new HttpRequestMessage();
requestMessage.Method = HttpMethod.Post;
requestMessage.Content = new ObjectContent<EnvelopeDefinition>(envelopeDefination, formatter, mediaType);
foreach (CELPHttpHeader header in headerList)
{
client.DefaultRequestHeaders.Add(header.Name, header.Value);
}
try
{
Task<HttpResponseMessage> webTaskResult = client.PostAsync(url, requestMessage.Content);
webTaskResult.Wait();
HttpResponseMessage response = webTaskResult.Result;
}
catch (Exception ex)
{
}
return (responseString);
}
A snippet of what the API request should look like is below:
--BOUNDARY
Content-Type: application/json
Content-Disposition: form-data
{
<JSON request here>
}
--BOUNDARY
Content-Type: application/pdf
Content-Disposition: file; filename="test1.pdf"; documentid=1
Content-Transfer-Encoding: base64
JVBERi0xLjUNJeLjz9MNCjMwMDIgMCBvYmoNPDwvTGluZWFyaXplZCAxL0wgMTM1
<snipped>
V1sxIDMgMF0+PnN0cmVhbQ0KaN5iYhRZU8PEwCDsBCQY1wMJpicAAQYAHeIDMQ0K
ZW5kc3RyZWFtDWVuZG9iag1zdGFydHhyZWYNCjEzNjA0NjUNCiUlRU9GDQo=
--BOUNDARY--
Related
Context
In my company we have a API that's very tricky to handle. I managed to make a successful PUT Request using Postman and now I want to build this same http request in C# using a simple Console application.
Here is the postman request:
The 2nd key has to be named exactly like that. The entry Json I can use via file or directly as value.
Here are the headers:
Only important one is the Authorization Header.
The problem
I don't know how to actually create this complicated request in C# since I'm very new to this language and couldn't find a solution to my specific problem.
I tried with the normal httpclient from C# and RestSharp but wasn't able to make this request.
Here is what I have so far:
{
class Program
{
static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
using var multipart = new MultipartFormDataContent();
var jsonBytes = JsonSerializer.SerializeToUtf8Bytes(new { Metadata = "abc" });
// Need to add my json file or the json direct here somewhere
// This is how the JSON looks like
/*
{
"values": {
"z1D_WorklogDetails": "very new workinfo 3",
"z1D_View_Access": "Internal",
"z1D Action": "MODIFY",
"z2AF_Act_Attachment_1": "UID Liste.xlsx"
}
}
*/
multipart.Add(new ByteArrayContent(jsonBytes), "entry");
using var fs = File.OpenRead(#"C:\myFile.txt");
multipart.Add(new StreamContent(fs), "attach-z2AF_Act_Attachment_1");
multipart.Headers.Add("Authorization", "//my token here");
using var resp = await client.PostAsync("**/entry/HPD:IncidentInterface/INC000001479529|INC000001479529", multipart);
resp.EnsureSuccessStatusCode();
}
}
}
So how can I make this complicated request like the on shown in Postman exactly the same in C#? The API Admins told me the attachment in attach-z2AF_Act_Attachment_1 has to come Base64 encrypted
For anyone that is interested what this call actually does:
It adds a new Worklog to an existing ticket in our ticket system (BMC Remedy) and also adds an attachment to this new worklog entry.
Thank you very much.
Please look at the code below, please test it at your environment.
The point is that you can set content types manually.
Another point is that you set Authorization header wrong.
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
string url = "https://localhost/";
string token = "token_here";
//Prepare json data
string json = JsonSerializer.Serialize(new { Metadata = "abc" });
StringContent jsonContent = new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json);
StreamContent streamContent;
bool base64 = false;
//Prepare octet-stream data
if (base64)
{
//For base-64 encoded message
using FileStream inputFile = new FileStream(#"2.txt", FileMode.Open, FileAccess.Read, FileShare.None,
bufferSize: 1024 * 1024, useAsync: true);
using CryptoStream base64Stream = new CryptoStream(inputFile, new ToBase64Transform(), CryptoStreamMode.Read);
streamContent = new StreamContent(base64Stream);
streamContent.Headers.Add("Content-Type", MediaTypeNames.Application.Octet);
await SendRequest(jsonContent, streamContent, url, token);
}
else
{
//For plain message
using FileStream file = File.OpenRead("2.txt");
streamContent = new StreamContent(file);
streamContent.Headers.Add("Content-Type", MediaTypeNames.Application.Octet);
await SendRequest(jsonContent, streamContent, url, token);
}
async Task SendRequest(StringContent stringContent, StreamContent streamContent, string url, string token)
{
// Add json and octet-stream to multipart content
MultipartFormDataContent multipartContent = new MultipartFormDataContent();
multipartContent.Add(stringContent, "entry");
multipartContent.Add(streamContent, "attach-z2AF_Act_Attachment_1");
//Create request
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, url);
//Here is the right setting of auth header value, sheme is on the left side
request.Headers.Authorization = new AuthenticationHeaderValue("AR-JWT", token);
request.Content = multipartContent;
//Last step - sending request
HttpClient http = new HttpClient();
HttpResponseMessage resp = await http.SendAsync(request);
resp.EnsureSuccessStatusCode();
}
With my approach i got this request:
Headers:
{
"content-length": "6538",
"authorization": "AR-JWT token_here",
"content-type": "multipart/form-data; boundary=\"02600173-b9af-49f4-8591-e7edf2c0b397\""
}
Body:
--02600173-b9af-49f4-8591-e7edf2c0b397
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=entry
{"Metadata":"abc"}
--02600173-b9af-49f4-8591-e7edf2c0b397
Content-Type: application/octet-stream
Content-Disposition: form-data; name=attach-z2AF_Act_Attachment_1
OCTET DATA HERE
--02600173-b9af-49f4-8591-e7edf2c0b397--
Is it correct?
Update: I've made a base-64 encoded version of attachment.
Simply set base64 to true.
Request with base64 approach:
Headers:
{
"content-length": "354",
"authorization": "AR-JWT token_here",
"content-type": "multipart/form-data; boundary=\"7572d1e8-7bd7-4f01-9c78-ce5b624faab3\""
}
Body:
--7572d1e8-7bd7-4f01-9c78-ce5b624faab3
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=entry
{"Metadata":"abc"}
--7572d1e8-7bd7-4f01-9c78-ce5b624faab3
Content-Type: application/octet-stream
Content-Disposition: form-data; name=attach-z2AF_Act_Attachment_1
MjIyMg==
--7572d1e8-7bd7-4f01-9c78-ce5b624faab3--
I am using C# 4.7.2 and am using a Console and not WinForms. I am trying to get an input of a user's image path then send a post request to a ShareX Image Hoster API.
How can I keep it plain and simple using void? EX:
public static void UploadImg(string ImagePath, string UploadAPI, string UploadKey) { }
ShareX Config:
{
"Version": "13.2.1",
"Name": "host",
"DestinationType": "ImageUploader",
"RequestMethod": "POST",
"RequestURL": "https://ADDRESS/upload",
"Headers": {
"token": "name_RANDOMSTRING",
"json": "true"
},
"Body": "MultipartFormData",
"Arguments": {
"imgfile": null
},
"FileFormName": "imgfile",
"URL": "$json:url$"
}
Capturing traffic with Fiddler I can use these headers:
POST https://IMAGEHOST/api/upload HTTP/1.1
token: SPECIALKEY
json: true
Content-Type: multipart/form-data; boundary=--------------------8d8ee229124e662
User-Agent: ShareX/13.4.0
Host: IMGHOSTER
Content-Length: 7518
Connection: Keep-Alive
----------------------8d8ee229124e662
Content-Disposition: form-data; name="imgfile"; filename="851TO25E8.png"
Content-Type: image/png
Then the rest after these headers is unknown ascii bytes nonsense.
The response is:
{"url":"https://FinalShortenedURL/"}
UPDATE - .Net 4.7.2
public static async Task UploadImg(string ImagePath, string UploadAPI, string UploadKey)
{
using (var client = new System.Net.Http.HttpClient())
{
// TODO: implement auth - this example works for bearer tokens:
// client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", UploadKey);
// Or you could use simple headers:
client.DefaultRequestHeaders.Add("token", UploadKey);
// inject the JSON header... and others if you need them
client.DefaultRequestHeaders.Add("json", "true");
var uri = new System.Uri(UploadAPI);
// Load the file:
var file = new System.IO.FileInfo(ImagePath);
if (!file.Exists)
throw new ArgumentException($"Unable to access file at: {ImagePath}", nameof(ImagePath));
using (var stream = file.OpenRead())
{
var multipartContent = new System.Net.Http.MultipartFormDataContent();
multipartContent.Add(
new System.Net.Http.StreamContent(stream),
"imgfile", // this is the name of FormData field
file.Name);
System.Net.Http.HttpRequestMessage request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, uri);
request.Content = multipartContent;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode(); // this throws an exception on non HTTP success codes
}
}
}
The following is the original posted solution for .Net Core to upload using multi-part:
public static async Task UploadImg(string ImagePath, string UploadAPI, string UploadKey)
{
using (var client = new Windows.Web.Http.HttpClient())
{
// TODO: implement auth - this example works for bearer tokens:
client.DefaultRequestHeaders.Authorization = new Windows.Web.Http.Headers.HttpCredentialsHeaderValue("Bearer", UploadKey);
// Or you could use simple headers:
client.DefaultRequestHeaders.Add("token", UploadKey);
// Load the file:
StorageFile file = await StorageFile.GetFileFromPathAsync(ImagePath);
var uri = new System.Uri(UploadAPI);
HttpMultipartFormDataContent multipartContent = new HttpMultipartFormDataContent();
multipartContent.Add(
new HttpStreamContent(stream),
"imgfile", // this is the name of FormData field
file.Name);
Windows.Web.Http.HttpRequestMessage request = new Windows.Web.Http.HttpRequestMessage(Windows.Web.Http.HttpMethod.Post, uri);
request.Content = multipartContent;
var response = await client.SendRequestAsync(request);
response.EnsureSuccessStatusCode(); // this throws an exception on non HTTP success codes
}
}
The process is similar in .Net framework, except that you can use System.IO for file operations.
More Information
Having a quick snoop around SO will find many similar questions with similar solutions or pratical advice. This answer is specifically provided to work with OPs ShareX configuration, but if you need further information have a read over these articles:
Http MultipartFormDataContent
C# HttpClient 4.5 multipart/form-data upload
c# MultipartFormDataContent Add methods (how to properly add a file)
Batch request returns 400 Bad Request.
I convert Japanese character to Shift-JIS: "description": 譌・ (shift_jis)
This is what it contains:
--batch_request
Content-Type:multipart/mixed;boundary=changeset_1
--changeset_1
Content-Type: application/http
Content-Transfer-Encoding:binary
Content-Id: 1
POST https://.... HTTP/1.1
Accept: application/json
Content-Type: application/json
{\"objectId\":\"5f6851c3-99cc-4a89-936d-4bb44fa78a34\",\"description\":\"譌・\"}
--changeset_1--
--batch_request--
Client:
public async Task<List<string>> POSTBatch(string endpoint, string contentbody)
{
HttpResponseMessage response = null;
HttpRequestMessage batchRequest = null;
HttpContent httpContent = null;
try
{
var requestcontent = new DemoRequestContent(contentbody);
httpContent = requestcontent.GetHttpContent(Encoding.UTF8, text/plain);
httpContent.Headers.Add(DemoConst.CSRF_NONCE, getCSRF_NONE().Result);
httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/mixed;boundary=batch_request");
var method = new HttpMethod("POST");
batchRequest = new HttpRequestMessage(method, baseURL + endpoint);
batchRequest.Content = httpContent;
response = await httpClient.SendAsync(batchRequest);
.............
// Spilt response
var multipartRespMsgs = getMultiPart(response);
...............
}
catch (Exception ex)
{
}
}
The key is to add a new content type "msgtype" header to the response:
private async Task<List<HttpResponseMessage>> getMultiPart(HttpResponseMessage response){
var multipartContent = await response.Content.ReadAsMultipartAsync();
var multipartRespMsgs = new List<HttpResponseMessage>();
foreach (HttpContent currentContent in multipartContent.Contents) {
// Two cases:
// 1. a "single" response
if (currentContent.Headers.ContentType.MediaType.Equals("application/http", StringComparison.OrdinalIgnoreCase)) {
if (!currentContent.Headers.ContentType.Parameters.Any(parameter => parameter.Name.Equals("msgtype", StringComparison.OrdinalIgnoreCase) && parameter.Value.Equals("response", StringComparison.OrdinalIgnoreCase))) {
currentContent.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("msgtype", "response"));
}
multipartRespMsgs.Add(await currentContent.ReadAsHttpResponseMessageAsync());
// The single object in multipartRespMsgs contains a classic exploitable HttpResponseMessage (with IsSuccessStatusCode, Content.ReadAsStringAsync().Result, etc.)
}
// 2. a changeset response, which is an embedded multipart content
else {
var subMultipartContent = await currentContent.ReadAsMultipartAsync();
foreach (HttpContent currentSubContent in subMultipartContent.Contents) {
currentSubContent.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("msgtype", "response"));
multipartRespMsgs.Add(await currentSubContent.ReadAsHttpResponseMessageAsync());
// Same here, the objects in multipartRespMsgs contain classic exploitable HttpResponseMessages
}
}
}
}
In my example, testString contains:
{\"objectId\":\"5f6851c3-99cc-4a89-936d-4bb44fa78a34\",\"description\":\"譌・\"}
When go to case 1:
multipartRespMsgs.Add(await currentContent.ReadAsHttpResponseMessageAsync()); // 400 Bad Request
UPDATE: Server log
org.apache.catalina.connector.ClientAbortException: java.io.IOException: An established connection was aborted by the software in your host machine
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:372)
at org.apache.catalina.connector.OutputBuffer.flushByteBuffer(OutputBuffer.java:841)
at org.apache.catalina.connector.OutputBuffer.append(OutputBuffer.java:746)
at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:407)
at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:385)
at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:96)
at wt.servlet.ServletRequestMonitor$CountingOutputStream.write(ServletRequestMonitor.java:2388)
at java.util.zip.GZIPOutputStream.finish(GZIPOutputStream.java:168)
at wt.servlet.CompressionFilter$GzippingResponse.finish(CompressionFilter.java:623)
at wt.servlet.CompressionFilter$GzippingResponse.close(CompressionFilter.java:394)
at wt.servlet.CompressionFilter.doFilter(CompressionFilter.java:302)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at wt.servlet.RequestInterrupter.doFilter(RequestInterrupter.java:335)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at wt.servlet.ServletRequestMonitor.doFilter(ServletRequestMonitor.java:1660)
at wt.servlet.ServletRequestMonitorFilter.doFilter(ServletRequestMonitorFilter.java:56)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:543)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.ajp.AjpProcessor.service(AjpProcessor.java:524)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:818)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1637)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.IOException: An established connection was aborted by the software in your host machine
at sun.nio.ch.SocketDispatcher.write0(Native Method)
at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51)
at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
at sun.nio.ch.IOUtil.write(IOUtil.java:65)
at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471)
at org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:136)
at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:101)
at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:157)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doWrite(NioEndpoint.java:1322)
at org.apache.tomcat.util.net.SocketWrapperBase.doWrite(SocketWrapperBase.java:692)
at org.apache.tomcat.util.net.SocketWrapperBase.writeBlocking(SocketWrapperBase.java:485)
at org.apache.tomcat.util.net.SocketWrapperBase.write(SocketWrapperBase.java:409)
at org.apache.coyote.ajp.AjpProcessor.writeData(AjpProcessor.java:1390)
at org.apache.coyote.ajp.AjpProcessor.access$900(AjpProcessor.java:58)
at org.apache.coyote.ajp.AjpProcessor$SocketOutputBuffer.doWrite(AjpProcessor.java:1508)
at org.apache.coyote.Response.doWrite(Response.java:600)
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:360)
... 35 more
Please give me the solution to this problem.
Thank you
I am getting this error even though I added media formatter as follows. I am testing with postman. Postman headers content-type is application/json and body is x-www-form-urlencoded. How can I fix it?
"ExceptionMessage": "No MediaTypeFormatter is available to read an
object of type 'Initiate' from content with media type 'text/html'.",
"ExceptionType": "System.Net.Http.UnsupportedMediaTypeException"
Here is my code sample:
[RoutePrefix("api/v1/pin")]
public class GameController : ApiController
{
// POST: api/Game
[HttpPost, Route("initiation")]
public async System.Threading.Tasks.Task<Initiate> PurchaseInitiationAsync([FromBody]Initiate value)
{
if (value == null)
{
var message = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format("Request is NULL! Please check your data.")),
ReasonPhrase = "Request is NULL! Please check your data."
};
throw new HttpResponseException(message);
}
HttpClient httpClient = new HttpClient();
HttpContent content = new StringContent(
JsonConvert.SerializeObject(value),
Encoding.UTF8,
"application/json"
);
HttpResponseMessage response =
await httpClient.PostAsync("http://test:1907/purchase_initiation", content);
var obj = response.Content.ReadAsAsync<Initiate>(
new List<MediaTypeFormatter>
{
new JsonMediaTypeFormatter()
}).Result;
return obj;
}
}
I can only reproduce this if the request content type hitting api/v1/pin/initiation is text/html. You say that your Postman settings are set to application/json and body is x-www-form-urlencoded, but from my testing if that was the case the exception you've shown above wouldn't be thrown.
I would double check that Postman is actually sending the correct content type header by opening the Postman console (CTRL+ALT+C) and inspecting the request headers.
I am in a situation where I know I can connect to an endpoint (using Postman chrome app) but I get an authentication error when I attempt it through HttpClient executing as WebJob on Azure.
public string ScanEndPoint()
{
string result;
using (var client = new HttpClient())
{
var requestContent = new MultipartFormDataContent();
var url = $"{Host}/{Path}";
requestContent.Add(new StringContent("*"), Version);
requestContent.Add(new StringContent("***"), Reference);
requestContent.Add(new StringContent("********"), Password);
var response = client.PostAsync(url, requestContent).Result;
result = response.Content.ReadAsStringAsync().Result;
}
return result;
}
The MultipartFormData is because I have to post the credentials in the body and not as headers. Clicking on the code link in Postman shows:
POST /*************.php HTTP/1.1
Host: *****-*******.****.******
Cache-Control: no-cache
Postman-Token: b574e803-1873-d7dd-ff10-bfc509991342
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="*"
**
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="***"
****
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="*********"
********************************
------WebKitFormBoundary7MA4YWxkTrZu0gW--
What steps do I need to take to replicate that postman request so that it works in code?
This is what we had to do to get it working:
using (var client = new HttpClient(handler: clientHandler, disposeHandler: true))
{
client.BaseAddress = new Uri(Host);
var url = $"{Path}";
var parameters = new Dictionary<string, string> {
{ "V", Version },
{ "ref", Reference },
{ "password", Password }
};
var encodedContent = new FormUrlEncodedContent(parameters);
var response = client.PostAsync(url, encodedContent).Result;
result = response.Content.ReadAsStringAsync().Result;
}
return result;
The handler sets up a proxy for local debug which should be irrelevant on Azure but I can remove it if that proves to be wrong.
Most of the posts I read about this suggested the same approach as #Jayendran pointed to. Any ideas on what the differences might be?
Note: we also had to re-arrange the Host and path so that the Host ended with a "/"