I am experimenting with the REST API for sending batch messages in Firebase Cloud Messaging. I prepared a multipart HTTP request in C#:
using var request = new HttpRequestMessage(HttpMethod.Post, "https://fcm.googleapis.com/batch");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "xxx");
request.Content = new StringContent(multicast);
request.Content.Headers.Remove("Content-Type");
request.Content.Headers.TryAddWithoutValidation("Content-Type", "multipart/mixed; boundary=--subrequest_boundary");
var response = await FcmHttpClient.SendAsync(request);
The string value of the multicast field above is an HTTP content similar the one provided in the Firebase documentation:
--subrequest_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary
Authorization: Bearer xxx
POST /v1/projects/myproject/messages:send
Content-Type: application/json
accept: application/json
{
"message":{
"topic":"global",
"notification":{
"title":"FCM Message",
"body":"This is an FCM notification message to device 0!"
}
}
}
--subrequest_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary
Authorization: Bearer xxx
POST /v1/projects/myproject/messages:send
Content-Type: application/json
accept: application/json
{
"message":{
"topic":"readers-club",
"notification":{
"title":"Price drop",
"body":"2% off all books"
}
}
}
--subrequest_boundary--
Firebase server returns Bad Request-400 with error message: "Failed to parse batch request, error: 0 items. Received batch body: --subrequest_boundary--" which indicates that Firebase directly handles the content with the terminating --subrequest_boundary--.
What could be the cause of the problem?
yesterday, i need write bash script to send bath FCM notifaction, and see your code #Ugur, thanks.
now its working, u need change
Content-Type", "multipart/mixed; boundary=--subrequest_boundary
to
Content-Type", "multipart/mixed; boundary=subrequest_boundary
the script
#!/bin/bash
curl \
-X POST \
-H "Authorization: Bearer [token_auth]" \
-H "Content-Type: multipart/mixed; boundary=subrequest_boundary" \
--data-binary #test2.txt \
https://fcm.googleapis.com/batch
and test2.txt, example send 2 notification
--subrequest_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary
Authorization: Bearer [token_auth]
POST /v1/projects/[project_name_firebase]/messages:send
Content-Type: application/json
accept: application/json
{
"message":{
"token":"[token_device]",
"notification":{
"title":"FCM Message",
"body":"This is an FCM notification message to device 0!",
}
}
}
--subrequest_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary
Authorization: Bearer [token_auth]
POST /v1/projects/[project_name_firebase]/messages:send
Content-Type: application/json
accept: application/json
{
"message":{
"token":"[token_device]",
"notification":{
"title":"FCM Message",
"body":"This is an FCM notification message to device 0!",
}
}
}
--subrequest_boundary--
change [project_name_firebase] with name your project in firebase
console example : project_3323.
change [token_device] with token target device.
change [token_auth] with your google auth credentials token.
Notification configuration: https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#AndroidConfig
to get [token_auth]
java :
https://developers.google.com/identity/protocols/oauth2/service-account#java_1
phyton:
https://developers.google.com/identity/protocols/oauth2/service-account#python_2
curl:
https://developers.google.com/identity/protocols/oauth2/service-account#httprest
code for get [token_auth], getToken.sh
#!/bin/bash
client_email="[email_project_firebase]"
service="https://oauth2.googleapis.com/token"
time=3600
scope="https://www.googleapis.com/auth/cloud-platform"
private_key=$(echo -ne "[private_key]")
datenow="$(date +%s)"
expired="$(( datenow + time ))";
header='{"alg": "RS256","typ": "JWT"}'
payload='{"iss": "'$client_email'","scope": "'$scope'","aud": "'$service'","exp": '$expired',"iat": '$datenow'}'
HEADEREnc=$( echo -n "${header}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )
PAYLOADEnc=$( echo -n "${payload}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )
data="${HEADEREnc}"."${PAYLOADEnc}"
signature=$( openssl dgst -sha256 -sign <(echo -n "${private_key}") <(echo -n "${data}") | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )
JWT="${data}"."${signature}"
jsonDataToken=$(curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion='$JWT https://oauth2.googleapis.com/token)
tokenAuth=$(echo -n $jsonDataToken |grep -Po '"access_token":.*?[^\\]"'|cut -d ':' -f2 |sed 's/"//g')
echo $tokenAuth
[email_project_firebase],[private_key] u can get it from file json in "firebase console" -> project name -> project setting or click gear icon -> account services -> click button "create new secret key"
list scope for other service
https://developers.google.com/identity/protocols/oauth2/scopes
Try to change your code
request.Content.Headers.TryAddWithoutValidation("Content-Type", "multipart/mixed; boundary=--subrequest_boundary");
to
request.Content.Headers.TryAddWithoutValidation("Content-Type", "multipart/mixed; boundary=subrequest_boundary");
The devil's in the details of building the Content. You can't simply create a string matching that payload, because certain pieces of it, like the headers and boundaries, are not technically considered part of the body by the HttpClient stack.
If you really need to do this with a raw HttpClient, take a look at how the Google APIs .NET client builds and sends a batch request. But I suspect that once you realize how cumbersome this is, you're going to conclude that you're best off using a higher-level SDK, such as firebase-admin-dotnet, which is mentioned in the same documentation you linked to and utilizes Google's .NET client under the hood.
Related
I use HttpLogging to log requests coming to my endpoints. I would like to log the whole request. I setup the HttpLogging in Program.cs
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpLogging();
And change logging level in appsettings.Development.json
"Microsoft.AspNetCore": "Information"
Then when I send a request from Postman or cURL I can see all the needed information but the RequestBody is ALWAYS empty (near the log end)
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Protocol: HTTP/1.1
Method: POST
Scheme: https
PathBase:
Path: /WeatherForecast
Accept: */*
Connection: keep-alive
Host: localhost:7269
User-Agent: PostmanRuntime/7.29.0
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 60
Postman-Token: [Redacted]
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'WebApplication1.Controllers.WeatherForecastController.Post (WebApplication1)'
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
Route matched with {action = "Post", controller = "WeatherForecast"}. Executing controller action with signature System.Collections.Generic.IEnumerable`1[WebApplication1.WeatherForecast] Post() on controller WebApplication1.Controllers.WeatherForecastController (WebApplication1).
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'WebApplication1.WeatherForecast[]'.
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
StatusCode: 200
Content-Type: application/json; charset=utf-8
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
Executed action WebApplication1.Controllers.WeatherForecastController.Post (WebApplication1) in 8.0904ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'WebApplication1.Controllers.WeatherForecastController.Post (WebApplication1)'
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[3]
RequestBody: //<----------------------------------------------- ALWAYS EMPTY
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[4]
ResponseBody: [{"date":"2022-03-17T18:05:11.5799408+01:00","temperatureC":41,"temperatureF":105,"summary":"Bracing"}]
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished HTTP/1.1 POST https://localhost:7269/WeatherForecast application/json 60 - 200 - application/json;+charset=utf-8 19.0485ms
However I know the body is there because when I try to read the body in controller and stop on breakpoint I can see the body loaded in the variable.
using var reader = new StreamReader(HttpContext.Request.Body);
var myBody = await reader.ReadToEndAsync(); // {"username": "josef","password": "MyPassword"}
There is a request cURL example generated from PostMan.
curl --location --request POST 'https://localhost:7269/WeatherForecast' \
--header 'Content-Type: application/json' \
--data-raw '{
"username": "josef",
"password": "MyPassword"
}'
I dont know if there is a step I missed or something like that. I tried changing different log levels, different request types, bodies and body types. The RequestBody is always empty in the log.
Edit 1
I just found that when I keep the body reader from above in controller and read from it, it actually log the correct RequestBody in the log.
Could someone explain this behaviour? I dont understand why it should log only when I manually read the body.
I have got this working (allbeit with Serilog over the top). I have these additions to your work in my code:
Registering the logging settings:
services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add(HeaderNames.Accept);
logging.RequestHeaders.Add(HeaderNames.ContentType);
logging.RequestHeaders.Add(HeaderNames.ContentDisposition);
logging.RequestHeaders.Add(HeaderNames.ContentEncoding);
logging.RequestHeaders.Add(HeaderNames.ContentLength);
logging.MediaTypeOptions.AddText("application/json");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});
Including some appsettings.json:
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Information",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
}
},
Probably just the appsettings are needed, although you definately need to code logging.MediaTypeOptions.AddText("multipart/form-data"); if you want a request form submission logged
I am experiencing the exact same error as described in question INVALID_REQUEST_PARAMETER on listStatus
However, unlike that OP, I am not using the REST API directly, but am using the C# SDK from https://www.nuget.org/packages/DocuSign.eSign.dll
It would appear that the SDK wrapper is not including the querystring parameters as described by the answer in the above linked post. Is there a workaround other than waiting for DocuSign to fix their SDK -- and where is the appropriate place to submit a bug for their SDK?
Per comment, here is a code sample:
var envelopesApi = new DocuSign.eSign.Api.EnvelopesApi();
var envelopeIds = incentivesWithPendingOffers.Select(i => i.new_OfferLetterEnvelopeID).ToList();
var envelopeInfos = await envelopesApi.ListStatusAsync(_tokenAccountId, new EnvelopeIdsRequest(envelopeIds), null);
Running fiddler to capture the outbound REST call being made by the SDK, I see this:
PUT https://demo.docusign.net/restapi/v2/accounts/[ REDACTED ]/envelopes/status HTTP/1.1
X-DocuSign-SDK: C#
Authorization: Bearer [ REDACTED ]
Accept: application/json
User-Agent: Swagger-Codegen/2.1.0/csharp
Content-Type: application/json
Host: demo.docusign.net
Content-Length: 96
Accept-Encoding: gzip, deflate
{"envelope_ids":["1d324bac-60ea-44b5-9b60-a5de14af3beb","5431d728-4918-4218-9c12-765b1c914724"]}
which returns the following response (which the SDK turns into a .NET Exception):
HTTP/1.1 400 Bad Request
Cache-Control: no-cache
Content-Length: 238
Content-Type: application/json; charset=utf-8
X-DocuSign-TraceToken: [ REDACTED ]
Date: Wed, 01 Aug 2018 20:43:58 GMT
Strict-Transport-Security: max-age=31536000; includeSubDomains
{
"errorCode": "INVALID_REQUEST_PARAMETER",
"message": "The request contained at least one invalid parameter. Query parameter 'from_date' must be set to a valid DateTime, or 'envelope_ids' or 'transaction_ids' must be specified."
}
When the previous answer was written, the SDK didn't support putting the list of envelope IDs in the call body. As of client version 3.1.3 this is now available.
List<string> envelopeIds = new List<string>();
envelopeIds.Add("2b62eb63-784a-4228-be02-876762ea6661");
envelopeIds.Add("406a9a15-c8e9-4227-8dd2-bd9a5318d4fd");
EnvelopeIdsRequest envelopeIdsRequest = new EnvelopeIdsRequest();
envelopeIdsRequest.EnvelopeIds = envelopeIds;
ListStatusOptions options = new ListStatusOptions();
options.envelopeIds = "request_body"; //the Options value controls the query string parameter
EnvelopesInformation envelopesInfo = envelopesApi.ListStatus(accountId, envelopeIdsRequest, options);
I wasn't able to use the envelope_ids=request_body parameter via the SDK, but I was able to get status of several envelopes at once. This would be a viable workaround as long as you're not requesting so many envelope IDs that the URL overflows.
EnvelopesApi.ListStatusChangesOptions lsco = new EnvelopesApi.ListStatusChangesOptions
{
envelopeIds = "fdd1122a-9c1b-4eef-9e24-25bb2cdf2eb2, fe1cb500-6a4c-4328-bf24-55806434852f, 5b1d3828-f8cd-4bba-87f0-538cb920db96"
};
EnvelopesInformation listStatusChanges = envelopesApi.ListStatusChanges(accountId, lsco);
results in an API call to
GET https://demo.docusign.net/restapi/v2/accounts/{{accountId}}/envelopes?envelope_ids=fdd1122a-9c1b-4eef-9e24-25bb2cdf2eb2%2C%20fe1cb500-6a4c-4328-bf24-55806434852f%2C%205b1d3828-f8cd-4bba-87f0-538cb920db96
I'm making a call to third-party api. Here's the third-party api info and what they expect:
POST /api/ HTTP/1.1
Host: testurl.com
Content-Type: multipart/form-data
curl https://testurl.com/api \
-H "Content-Type: multipart/form-data" \
-F "document[description]=meeting notes" \
-F "document[matter][id]=123" \
-F "document[document_category][name]=Offers" \
-F "document_version[last_modified_at]=2013-12-03T23:35:32+00:00" \
-F "document_version[uploaded_data]=#test.txt"
I need to write a c# equivalent post method to send these info. I have taken care of the header in the curl call, but I'm not sure on the rest of the form-data. For instance, the "document" and "document_version" along with their respective attributes (or whatever they are), how do I pass those info along?
Here's what I found: http://www.briangrinstead.com/blog/multipart-form-post-in-c
I did exactly what's done in that link, but got back Bad Request error. More specific error:
{"error":"api error","message":"undefined method `key?' for nil:NilClass"}
I have no idea what's going on in the third-api so I don't know what this error means. Also, I'm trying to post pdf doc.
You can use the HttpClient
var client = new HttpClient();
var image = File.ReadAllBytes("c:\\test.png");
var formData = new MultipartFormDataContent();
formData.Add(new StreamContent(new MemoryStream(image)), "name","fileName.png");
formData.Add(new StringContent("content"), "name");
var response = client.PostAsync("http://localhost:5001/api/someMethod", formData).Result;
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
Console.WriteLine(content);
}
This works good for me.
I have shared Documentation to upload file from this link.
Upload File
Uploads the file content along with its metadata information.
POST : {{contentUrl}}/nodes?suppress={suppress}
suppress : (Optional)
deduplication: disables checking for duplicates when uploading Body Parameters:
Multi-form part
--------- metadata ------------
name (required) : file name. Max to 256 Characters. kind (required) : "FILE" labels (optional) : Extra information which is indexed. For example the value can be "PHOTO" properties (optional) : List of properties to be added for the file. parents(optional) : List of parent Ids. If no parent folders are provided, the file will be placed in the default root folder.
---------content ------------
File Bytes
Sample Request:
POST /cdproxy/nodes?localId=testPhoto
HTTP/1.1
Host: content-na.drive.amazonaws.com
Authorization: Bearer
Atza|IQEBLjAsAhReYeezFcFdYzqrFOJGv3EG
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="metadata"
{"name":"fooo.jpg","kind":"FILE"}
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="content";
filename="db5df4870e4e4b6cbf42727fd434701a.jpg"
Content-Type: image/jpeg
----WebKitFormBoundaryE19zNvXGzXaLvS5C
**cURL Request:**
curl -v -X POST --form
'metadata={"name":"testVideo1","kind":"FILE"}' --form
'content=#sample_iTunes.mp4'
'https://content-na.drive.amazonaws.com/cdproxy/nodes?localId=testVideo1&suppress=deduplication'
--header "Authorization: Bearer
Atza|IQEBLjAsAhQ5zx7pKp9PCgCy6T1JkQjHHOEzpwIUQM"
Fiddler:
I have written code and sent request.
This is the request i have sent using Fiddler
POST
https://content-na.drive.amazonaws.com/cdproxy//nodes?suppress=deduplication
HTTP/1.1 Authorization: Bearer
Atza|IQEBLjAsAhQXfR4gxrstIKB8jjCbbsTIRhad1QIUZgLJFXgenPSsYLp6VHeL2rEBQ6RFSQYAR847Pvr8bnF4wc-qAlP2oKbAxogI_KJBopMzbTwt3n1DgJ9D8VJTikjdkXH7_595n4ElGhAhKJybhiXBlZLDh7ScXiFC6g4MFfkVRMKoKWAf4gRhhYGTLX2nBSHWIYx5kpzbS0QNgOUS_Hluodfq0j5gf8FeNB3YG6q-KiQMdPbcXJFlKs28f_cSWnQvYyLu0c8YzcJZpjL3CTrabnGpF5KOM6ie71Q2mS3ncL71tEO5wu3MVEJZZtg00MKM5nkGAzH1hCHqA0PWmsLsQVm0X684Seje7aSMYDlh0hcRpgc4Y7fusEndnprz5EO9FftNDN3lbvxszgAR0DhoOg
Content-Type: multipart/form-data Host: content-na.drive.amazonaws.com
Content-Length: 2100825 Expect: 100-continue
------WebKitFormBoundary8d2ff0fafd6a750 Content-Disposition: form-data; name="metadata"
{"name":"C:\Users\jafar.baltidynamolog\Downloads\SampleVideo_360x240_2mb.mp4","kind":"FILE"}
------WebKitFormBoundary8d2ff0fafd6a750 Content-Disposition: form-data; name="content";
filename="C:\Users\jafar.baltidynamolog\Downloads\SampleVideo_360x240_2mb.mp4" Content-Type: video/mp4
��� ftypisom���isomiso2avc1mp41���free��LmdatT Lj����L5
��P�L"�:�ԮoUNx��V���qFM4��w�ח�l�iݧ����o��͑s����z���/e��ۢ�a���G��y�j�W-&{x�w��>�I���6�v�u�j;��pk#�=�K�ZO.a�yy
-w(���ژ�#S�Rpt<{�ڿZ38xt���x�[��ɒ�&�lsT��4P��rMXv��Ѹ%A�5���C�hH& ���P� Z���翷�n�����z���5t��D��M%�}���i���P|�k��#�>�#�tq�O�K���sy��=�\��9���)���Ɏ��������T��(��=v����
)D���R;zKBx��PU� ���E�/bB��)p�h,6��`��� ���XH2
Problem is that i am getting Error 500 internal Error. Kindly help me understand if some thing is missing or wrong. I am using this code to upload video. Kindly help me
I hava a WCF server written in C# and .NET 4.0
I'm trying to send a post request but I didn't receive answer from the browser.
This is my angular service.
services.factory('CodiciComunicazioneCreaFactory', function ($resource) {
return $resource('http://myIpAddress:port/service/create', { port : ':8002' }, {
create: { method: 'POST',
headers: { 'Content-Type': 'application/json' } }
});
});
I tried also to call my ws from curl with this command and all works perfectly.
curl -H "Content-Type: application/json" -d '{"Code":"aaa"}' http://myIpAddress:8002/servive/create
** EDIT **
Curl Output setting in XML response in the WCF.
~$ curl -H "Content-Type: application/json" -d '{"Code":"aaa"}' http://192.168.1.147:8002/CodiciComunicazione/create
<boolean xmlns="http://schemas.microsoft.com/2003/10/Serialization/">true</boolean>
Setting in the WCF server the response as Json, with the same command, curl return simply
true