Request body goes missing when application is deployed to Azure - azure-web-app-service

I have a net6.0 Razor pages app that is running fine locally, but doesn't work when deployed to Azure. It seems that POST bodies are going missing.
Here's some middleware...
public async Task Invoke(HttpContext context)
{
_logger.LogInformation("BodyLogger");
if (!context.Request.Body.CanSeek)
{
context.Request.EnableBuffering();
}
if (context.Request.Method == "POST")
{
_logger.LogInformation("POST cl=" + context.Request.ContentLength.ToString());
try
{
_logger.LogInformation("BodyLogger try");
string body = "";
context.Request.Body.Position = 0;
using (var reader = new StreamReader(context.Request.Body, leaveOpen: true))
{
_logger.LogInformation("BodyLogger using");
body = await reader.ReadToEndAsync();
}
context.Request.Body.Position = 0;
_logger.LogInformation($"BODY ok (length {body?.Length ?? 0})" + (body ?? "(null)"));
}
catch (Exception e)
{
_logger.LogError("BODY error " + e.Message);
throw;
}
}
await _next.Invoke(context);
}
...and what appears in ApplicationInsights.
What's going on?!
Updage
Even more strangely: it still doesn't work if it's deployed to a different Azure Web App resource, and this middleware
app.Use(async (HttpContext context, RequestDelegate next) =>
{
if (context.Request.Path == "/post")
{
if (!context.Request.Body.CanSeek)
{
context.Request.EnableBuffering();
}
//context.Request.Body.Position = 0;
context.Request.Body.Seek(0, SeekOrigin.Begin);
string body = "";
using (var reader = new StreamReader(context.Request.Body, leaveOpen: true))
{
body = await reader.ReadToEndAsync();
}
//context.Request.Body.Position = 0;
context.Request.Body.Seek(0, SeekOrigin.Begin);
await context.Response.WriteAsJsonAsync("The body was: " + body);
return;
}
await next.Invoke(context);
});
works both as the first and the last middleware.
I've noticed that absent from the ApplicationInsights logs are the Kestrel messages
Connection id "0HMOAORGGN3ES", Request id "0HMOAORGGN3ES:00000003": started reading request body.
Connection id "0HMOAORGGN3ES", Request id "0HMOAORGGN3ES:00000003": done reading request body.

It turns out that in some cases my custom ITelemetryInitializer for ApplicationInsights (turned off in development) was reading the request body.

Related

MAUI Http calls to local API freeze on android device, but not on windows

For some reason my http calls to local api freezes on await when running MAUI application on my android device, but works as intended when running it on windows.
The flow:
public ICommand LoginCommand => _loginCommand ??=
new Command(async () =>
{
if (await _userService.Login(Username, Password))
{
Navigate("//chatrooms");
}
});
public async Task<bool> Login(string username, string password)
{
try
{
var response = await _barigaProApiClient.SignIn(
new SignInRequest
{
Username = username,
Password = password
});
}
catch (Exception)
{
return false;
}
return true;
}
public async Task<SignInResponse> SignIn(SignInRequest request)
{
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
var response = await _client.PostAsync("/api/User/SignIn", content); // stuck here
response.EnsureSuccessStatusCode();
return JsonConvert.DeserializeObject<SignInResponse>(await response.Content.ReadAsStringAsync());
}
At first I thought it could be a deadlock and unmanaged synchronization context.
Found an article with similar problem, got a suggestion to use .ConfigureAwait(false) to fix that, but it still did not work.

How to sign oauth signature for request token with etrade api?

I'm trying to hook into eTrade's API for a project. They use an OAuth v1 flow. I'm running into error's on the very first step of the flow- getting the request token. The info from their doc's can be found here.
The error I'm getting is 401- invalid signature.
I've been reading up on the OAuth flow here and have become familiar with the process. In the past I've used PassportJS for 3rd party integrations.
So eTrade has provided me 4 keys- PROD_KEY, PROD_SECRET, SANDBOX_KEY, SANDBOX_SECRET. I'm using the sandbox keys for now as my production key is still pending.
Now I have an endpoint on my API that I'll call from my client to get the request tokens. It's structure like this:
router.route('/auth/request').get(controller.request);
And the following controller is where I'm generating a signature and trying to request the token from eTrade. That controller looks like this:
// controllers/auth.js
const axios = require('axios');
const { v1 } = require('uuid');
const crypto = require('crypto');
function generateSignature() {
const method = 'GET',
url = 'http://localhost/',
encodedUrl = encodeURIComponent(url),
paramaters = {
oauth_consumer_key: process.env.ETRADE_SANDBOX_API_KEY,
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: Math.floor(Date.now() / 1000),
oauth_nonce: v1(),
oauth_callback: 'oob'
}
var ordered = {};
Object.keys(paramaters).sort().forEach((key) => {
ordered[key] = paramaters[key];
});
var encodedParameters = '';
for(k in ordered) {
const encodedValue = escape(ordered[k]);
const encodedKey = encodeURIComponent(k);
if(encodedParameters === '') {
encodedParameters += encodeURIComponent(`${encodedKey}=${encodedValue}`);
} else {
encodedParameters += encodeURIComponent(`&${encodedKey}=${encodedValue}`);
}
}
encodedParameters = encodeURIComponent(encodedParameters);
const signature_base_string = `${method}&${encodedUrl}&${encodedParameters}`;
const signing_key = `${process.env.ETRADE_SANDBOX_SECRET_KEY}`;
const signature = crypto.createHmac("SHA1", signing_key).update(signature_base_string).digest().toString('base64');
const encodedSignature = encodeURIComponent(signature);
return encodedSignature;
}
module.exports = {
request: async (req, res) => {
console.log('Fetching request token from etrade...');
try {
var response = await axios.get(`https://api.etrade.com/oauth/request_token`, {
params: {
oauth_consumer_key: process.env.ETRADE_SANDBOX_API_KEY,
oauth_signature_method: 'HMAC-SHA1',
oauth_signature: generateSignature(),
oauth_timestamp: Math.floor(Date.now() / 1000),
oauth_nonce: v1(),
oauth_callback: 'oob'
}
});
console.log('Fetched request token...', response.data);
} catch (error) {
console.log('Could not fetch request token...', error.response.data);
}
}
}
I have followed some guides on the internet to help me understand the process of signing a request, but I can't seem to get a valid response back.
Your code is targeting the etrade production URL. Try calling the sandbox URL.
Production https://api.etrade.com/v1/{module}/{endpoint}
Sandbox https://apisb.etrade.com/v1/{module}/{endpoint}
I don't know if you are still looking for an answer on this, but I think the following might help. I ran into a similar issue while I was trying to integrate with their API (I am using java though). Looking into their sample code, I realized that the keys to sign the baseString requires an extra '&' at the end for request_token API. See the following code from their sample application -
if( token != null){
// not applicable for request_token API call
key = StringUtils.isEmpty(token.getOauth_token_secret()) ? context.getResouces().getSharedSecret() +"&" :
context.getResouces().getSharedSecret()+"&" + OAuth1Template.encode(token.getOauth_token_secret());
}else{
// applicable for request_token API call
key = context.getResouces().getSharedSecret() +"&";
}
AFAIK, this is not oAuth standard or anything. This seems to be very specific to their implementation. Anyways, I updated my code to include the extra '&' at the end & it started working.
private String signBaseString(String httpMethod, String url) {
StringBuilder baseString = new StringBuilder();
baseString.append(encode(httpMethod)).append("&").append(encode(url)).append("&");
baseString.append(encode(normalizeParams(url)));
// the extra & is added here.
SecretKeySpec signingKey = new SecretKeySpec((this.apiSecret + "&").getBytes(), SIGN_ALG);
Mac mac = null;
try {
mac = Mac.getInstance(SIGN_ALG);
mac.init(signingKey);
return new String(Base64.encodeBase64(
mac.doFinal(baseString.toString().getBytes(
StandardCharsets.UTF_8))));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
logger.error("Error during signing headers", e);
return null;
}
}
Let me know if this works for you.

Convert Azure Function to WebAPI call Azure Storage

I've been trying to convert the functions in this https://blog.jeremylikness.com/build-a-serverless-link-shortener-with-analytics-faster-than-finishing-your-latte-8c094bb1df2c to a WebAPI equivalent. This is my webapi call:
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody] ShortRequest shortRequest)
{
_logger.LogInformation($"ShrinkUrl api called with req: {shortRequest}");
if(!Request.IsHttps && !Request.Host.Host.Contains("localhost"))
return StatusCode(StatusCodes.Status400BadRequest);
if (string.IsNullOrEmpty(shortRequest.Input))
return StatusCode(StatusCodes.Status404NotFound);
try
{
var result = new List<ShortResponse>();
var analytics = new Analytics();
// determine whether or not to process analytics tags
bool tagMediums = analytics.Validate(shortRequest);
var campaign = string.IsNullOrWhiteSpace(shortRequest.Campaign) ? DefaultCampaign : shortRequest.Campaign;
var url = shortRequest.Input.Trim();
var utm = analytics.TagUtm(shortRequest);
var wt = analytics.TagWt(shortRequest);
_logger.LogInformation($"URL: {url} Tag UTM? {utm} Tag WebTrends? {wt}");
// get host for building short URL
var host = Request.Scheme + "://" + Request.Host;
await _tableOut.CreateIfNotExistsAsync();
if (_keyTable == null)
{
_logger.LogInformation($"Keytable is null, creating initial partition key of 1");
_keyTable = new NextId
{
PartitionKey = "1",
RowKey = "KEY",
Id = 1024
};
var keyAdd = TableOperation.Insert(_keyTable);
await _tableOut.ExecuteAsync(keyAdd);
}
// strategy for getting a new code
string getCode() => Utility.Encode(_keyTable.Id++);
// strategy for logging
void logFn(string msg) => _logger.LogInformation(msg);
// strategy to save the key
async Task saveKeyAsync()
{
var operation = TableOperation.Replace(_keyTable);
await _tableOut.ExecuteAsync(operation);
}
// strategy to insert the new short url entry
async Task saveEntryAsync(TableEntity entry)
{
var operation = TableOperation.Insert(entry);
await _tableOut.ExecuteAsync(operation);
}
// strategy to create a new URL and track the dependencies
async Task saveWithTelemetryAsync(TableEntity entry)
{
await TrackDependencyAsync(
"AzureTableStorageInsert",
"Insert",
async () => await saveEntryAsync(entry),
() => true);
await TrackDependencyAsync(
"AzureTableStorageUpdate",
"Update",
async () => await saveKeyAsync(),
() => true);
}
if (tagMediums)
{
// this will result in multiple entries depending on the number of
// mediums passed in
result.AddRange(await analytics.BuildAsync(
shortRequest,
Source,
host,
getCode,
saveWithTelemetryAsync,
logFn,
HttpUtility.ParseQueryString));
}
else
{
// no tagging, just pass-through the URL
result.Add(await Utility.SaveUrlAsync(
url,
null,
host,
getCode,
logFn,
saveWithTelemetryAsync));
}
_logger.LogInformation($"Done.");
//return req.CreateResponse(HttpStatusCode.OK, result);
}
catch (Exception ex)
{
_logger.LogError("An unexpected error was encountered.", ex);
//return req.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
}
return null;
}
And this is the function params:
[FunctionName("ShortenUrl")]
public static async Task<HttpResponseMessage>([HttpTrigger(AuthorizationLevel.Function, "post")],HttpRequestMessage req,
[Table(Utility.TABLE, "1", Utility.KEY, Take = 1)]NextId keyTable,
[Table(Utility.TABLE)]CloudTable, TraceWriter log)
The azure function takes care of ensuring that the keyTable contains the next Id in the counter but I cannot figure out how to do the same in the webapi call.
Any ideas?

Node JS Soap to send a file to sharepoint based webserver using CopyIntoItems

I am writing a Node JS SOAP client using Node-Soap module to send a file to a remote SharePoint based Web Services.
The machine client requires a proxy to access Internet and the SharePoint WS requires an account (user, pwd). Below are the code source.
However, I always have the error "(node:20857) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Cannot parse response".
Someone can help me, please?
var process = require('process');
var fs = require('fs');
var request = require('request')
var soap = require('soap');
var apiWSDL = '.../test-collab/WS/_vti_bin/copy.asmx?wsdl';
function sendFile() {
var p = new Promise(function (resolve, reject) {
request_with_defaults = request.defaults({
'proxy': 'http://***:***#10.115.108.109:8080',
'timeout': 50000,
'connection': 'keep-alive'
});
var options = {
'request': request_with_defaults,
endpoint: 'https://.../test-collab/WS/_vti_bin/copy.asmx',
}
var byteArray = fs.readFileSync('test.txt').toString('base64');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
//process.env.https_proxy = 'http://***#***:10.115.108.109:8080';
soap.createClient(apiWSDL, options, function (err, client) {
if (err) throw new Error(err);
var args = {
DestinationUrls: 'https://.../test-collab/WS/CAS/test.txt',
Stream: byteArray
}
client.setSecurity(new soap.ClientSSLSecurity(null, null, null, { /*default request options like */
strictSSL: false,
rejectUnauthorized: false,
// hostname: 'some-hostname'
//secureOptions: constants.SSL_OP_NO_TLSv1_2,
forever: true,
}));
client.addHttpHeader('vm6_webapp', 'SERVICE');
client.addHttpHeader('vm6_password', '***');
client.addHttpHeader('vm6_user', '***');
client.CopyIntoItems(args, function (err, result) {
//console.log(err);
if (err) {
console.log(err);
reject(err);
}
var sets;
try {
console.log(result);
if (result.length) {
resolve(result);
} else {
reject(result);
}
} catch (error) {
console.log("error");
reject("error und")
}
});
});
});
return p;
}
As the error message is Cannot parse response, two possibilities can happen:
either the response is not in XML format
or the XML response is not acceptable by SOAP response message defined in wsdl.
Can you redo the SOAP request by using an existing client, such as, SoapUI to confirm?
Otherwise, I propose to use console.error( err.stack ) rather than console.log( err ) to get the full execution trace of err.
Thank Nghia for your reply.
In fact, I've written a SOAP client in Java for this WebServers before and it works. That means the paramters are ok as well as the XML response is ok.
Here are the code in Java:
MoccaClient clientWSMocca = new MoccaClient();
CopySoap copySoap = (CopySoap)clientWSMocca.getClient(site_soap_url,
proxy_host, proxy_port, proxy_user, proxy_password,
mocca_user, mocca_password, mocca_web_app,
CopySoap.class);
// Récupération sous forme de tableau de bytes du fichier
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
InputStream in = new BufferedInputStream(new FileInputStream(file_path));
BufferedOutputStream bufOut = new BufferedOutputStream(out);
for (int b = in.read(); b != -1; b = in.read()) {
bufOut.write(b);
}
in.close();
bufOut.close();
} catch (Exception e) {
e.printStackTrace();
}
// Initialisation des variables de contexte
FieldInformation fieldInformation = new FieldInformation();
String destinationUrl = site_url + file_name;
DestinationUrlCollection destinationUrlCollection = new DestinationUrlCollection();
destinationUrlCollection.getString().add(destinationUrl);
FieldInformationCollection fieldInformationCollection = new FieldInformationCollection();
fieldInformationCollection.getFieldInformation().add(fieldInformation);
Holder<CopyResultCollection> copyResult= new Holder<CopyResultCollection>();
Holder<Long> getItemResult = new Holder<Long>();
copySoap.copyIntoItems(file_path, destinationUrlCollection, fieldInformationCollection, out.toByteArray(), getItemResult, copyResult);
MoccaClient.java
public class MoccaClient {
public Object getClient(String url,
String proxyhost, int proxyport, String userproxy, String passwordproxy,
String moccauser, String moccapassword, String moccawebapp,
Class<?> serviceclass) {
System.setProperty("org.apache.cxf.JDKBugHacks.defaultUsesCaches", "true");
boolean bssl = false;
if (url.startsWith("https")) {
bssl = true;
}
if (url.startsWith("HTTPS")) {
bssl = true;
}
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.getInInterceptors().add(new LoggingInInterceptor());
factory.getOutInterceptors().add(new LoggingOutInterceptor());
factory.getInInterceptors().add(new MyInterceptor());
factory.setServiceClass(serviceclass);
factory.setAddress(url);
Object client = factory.create();
Client clientDuProxy = ClientProxy.getClient(client);
Map<String, List<String>> headers = new HashMap();
headers.put("vm6_user", Arrays.asList(moccauser));
headers.put("vm6_password", Arrays.asList(moccapassword));
headers.put("vm6_webapp", Arrays.asList(moccawebapp));
clientDuProxy.getRequestContext().put(Message.PROTOCOL_HEADERS, headers);
HTTPConduit http = (HTTPConduit)clientDuProxy.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
http.setClient(httpClientPolicy);
if (proxyhost != null) {
http.getClient().setProxyServer(proxyhost);
http.getClient().setProxyServerPort(proxyport);
}
if (userproxy != null) {
http.getProxyAuthorization().setUserName(userproxy);
http.getProxyAuthorization().setPassword(passwordproxy);
}
if (bssl) {
TrustManager[] trustCerts = new TrustManager[]{new AllTrust()};
TLSClientParameters tcp = new TLSClientParameters();
tcp.setTrustManagers(trustCerts);
tcp.setSecureSocketProtocol("TLS");
tcp.setDisableCNCheck(true);
http.setTlsClientParameters(tcp);
}
return client;
}
}

Subscribing to Azure Push Notification service in Xamarin Forms

I tried to integrate with Push Notifications to my forms application. Azure messaging component is used for achieving this.
Below is the code i am using. I am getting the trigger to RegisteredForRemoteNotifications method. But RegisterNativeAsync method doesn't seem to be doing the job.
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var push = UIUserNotificationSettings.GetSettingsForTypes(UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound,
new NSSet());
UIApplication.SharedApplication.RegisterUserNotificationSettings(push);
UIApplication.SharedApplication.RegisterForRemoteNotifications();
}
else
{
const UIRemoteNotificationType not = UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound;
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(not);
}
}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
Hub = new SBNotificationHub(conStirng, NotifHubPath);
Hub.UnregisterAllAsync(deviceToken, (error) =>
{
//Get device token
var id = deviceToken.ToString();
var tag = "username";
var tags = new List<string> { tag };
Hub.RegisterNativeAsync(id, new NSSet(tags.ToArray()), (errorCallback) =>
{
if (errorCallback != null)
{
//Log to output
}
});
});
}
What am i doing wrong here? How can i confirm if the Register function is success or failure.?
You need to check if the error from the response of the register method is null or not. if it is null means the it is a success.
var hub = new SBNotificationHub (cs, "your-hub-name");
hub.RegisterNativeAsync (deviceToken, null, err => {
if (err != null)
Console.WriteLine("Error: " + err.Description);
else
Console.WriteLine("Success");
});
In the case of windows universal apps we can check the registrationId property of the response.
private async void InitNotificationsAsync()
{
var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
var hub = new NotificationHub("<hub name>", "<connection string with listen access>");
var result = await hub.RegisterNativeAsync(channel.Uri);
// Displays the registration ID so you know it was successful
if (result.RegistrationId != null)
{
var dialog = new MessageDialog("Registration successful: " + result.RegistrationId);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
}

Resources