Enabling binary media types breaks Option POST call (CORS) in AWS Lambda - asp.net-core

New to AWS..
We have a .NET Core Microservice running on a serverless aws instance as lambda functions.
Our Controller looks like this
[Route("api/[controller]")]
[ApiController]
public class SomeController : ControllerBase
{
[HttpGet()]
[Route("getsomedoc")]
public async Task<IActionResult> GetSomeDoc()
{
byte[] content;
//UI needs this to process the document
var contentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
contentDisposition.FileName = "File Name";
Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
return File(content, "application/octet-stream");
}
[HttpPost()]
[Route("somepost")]
public async Task<IActionResult> SomePost()
{
return null;
}
}
URL's
{{URL}}/getsomedoc
{{URL}}/somepost
We have enabled 'Binary Media Types' in AWS package settings to / for the getsomedoc to work otherwise it was returning the byte array back instead of the file.
But this is breaking our 'somepost' call when UI is accessing the API using
Method: OPTIONS & Access-Control-Request-Method as POST
When we remove the binary media type the 'somepost' starts working.
Looking for suggestions as why this might be happening? and what can we add/remove from gateway to get this fixed.

Well we ended up resolving this in a strange way.
Added two gateways for the lambda
- on one of them have binary enabled
- Disabled on the other one.
For
getsomedoc - Using the one where binary media types are enabled
postsomedoc - Using the other one
Wish there was a better way!!

I have found this same behavior with my API. While looking everywhere for some help, I found a few things that address the issue:
Basically, this bug report says the problem is having CORS enabled while also using the generic Binary Media Type "*/*". Apparently the OPTIONS method gets confused by this. They discuss this in terms of using Serverless, but it should apply to using the console or other ways of interacting with AWS.
They link to a possible solution: you can modify the Integration Response of the OPTIONS method - change the Mapping Template's Content-Type to an actual binary media type, like image/jpeg. They say this allows you to leave the binary media type in Settings as "*/*". This is a little hacky, but at least it is something.
There also was this alternate suggestion in the issues section of this GitHub repo that is a little less hacky. You can set the content handling parameter of the OPTIONS Integration Request to "CONVERT_TO_TEXT"... but you can only do this via CloudFormation or the CLI (not via the console). This is also the recommended solution by some AWS Technicians.

Another possible workaround is to setup a custom Lambda function to handle the OPTIONS request, this way the API gateway may have the "*/*" Binary Media Type.
Create a new lambda function for handling OPTIONS requests:
exports.handler = async (event) => {
const response = {
statusCode: 200,
headers:{
'access-control-allow-origin':'*',
'Access-Control-Allow-Headers': 'access-control-allow-origin, content-type, access-control-allow-methods',
'Access-Control-Allow-Methods':"GET,POST,PUT,DELETE,OPTIONS"
},
body: JSON.stringify("OK")
};
return response;
};
In your API Gateway OPTION method, change the integration type from Mock to Lambda Function.
Make sure to check 'Use Lambda proxy integration'
Select the correct region and point to the created Lambda Function
This way any OPTIONS request made from the browser will trigger the Lambda function and return the custom response.
Be aware this solution might involve costs.

Related

Can't perform HTTP Post Action from Logic App to Asp.net Core Web API

I've built many Logic Apps. I've also integrated with the Logic App API. For some reason, a Post request to an Asp.net Core Web API won't work. It works in Postman, but I can't get Logic Apps to complete the request.
The request arrives at my Web API. I can step through it during a remote debug session. I'm using the [FromBody] decorator on the API method. All the string values in the object are null.
Logic App Headers
Accept = "application/json"
ContentType = "application/json"
ContentLength = "35"
Host = "****.centralus.logic.azure.com"
API method
[HttpPost]
[Route("CreateSomething")]
public async Task<IActionResult> CreateSomething([FromBody] MyObject object)
{
//Create something great
}
I think it might have something to do with the Headers. I noticed that the Postman request won't succeed unless I check the Host and Content-Length box in the Headers section. According to this article, Logic Apps ignores those Headers.
https://learn.microsoft.com/en-us/azure/connectors/connectors-native-http
I've built the HTTP Post Action using the API as well as configured it manually using the Logic App UI in Azure.
By the way, does anyone know the Expression that will automatically calculate the ContentLength?
UPDATE:
I finally figured this out. I had to do some Ninja coding crap to make this work. I'll post my solution tomorrow.
Does anyone know how to make this work? Thanks in advance!
When you use the Logic App API to programmatically create Logic Apps, you have to specify the Body class for when you do something like an HTTP Post. When the Body JSON displayed in the designer, it contained a single object with the objects properties. My API method could not handle this. The key was to simply post the properties in the JSON Body. To make matters worse, I'm doing two HTTP Posts in this particular Logic App. When I tried to add my object properties to the existing Body class, it caused my other HTTP Post to stop working. To overcome this, I had to create a Body2 class with the objects properties. I then had to use the following line of code to replace body2 with body before adding the JSON to the Logic App API call.
This did not work.
body = new Body()
{
object = new Object()
{
//Properties
}
}
This worked.
body2 = new Body2()
{
Type = 0,
Description = "#{items('For_each_2')?['day']?['description']}",
Locations = item.Locations,
Cold = "#{items('For_each_2')?['temperature']?['cold']?['value']}",
Hot = "#{items('For_each_2')?['temperature']?['hot']?['value']}",
Hide = 0
}
Notice I used Replace on body2.
var options = new JsonSerializerOptions { WriteIndented = true, IgnoreNullValues = true};
string jsonString = ReplaceFirst(JsonSerializer.Serialize(myApp, options), "schema", "$schema").Replace("_else", "else").Replace("_foreach", "foreach").Replace("body2", "body");

How to pass request headers set using headers option for ServerSideEvent (If Platform is browser) in React js

I have got to know that, the new npm package #microsoft/signalR provided options to pass custom header to httpClient used to make SSE calls in javascript (using headers option in withUrl).
But found a difference in the code (git code) where I see, the same custom header isn't forwarded if the request is from Browser or WebWorker. If otherwise, it is forwareded (git code)
I would like to understand, is there a security reason for not forwarding the header? If yes, is there a way to get it working? i.e, set custom header when making HTTP requests if the transport type is SSE (ServerSentEvent).
The reason is because browsers do not support sending headers with EventSource
https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource
Answering my question for future readers.
I have got it working for my requirement, Where I need to pass custom headers to all the signalR calls irrespective of transport type, starting from the negotiate call.
I could be able to send the custom header using the headers option while creating hubConnectionBuilder.withUrl(url, options) (have given detailed answer here)
To the point:
For SSE, as mentioned by Brennan, we cant set the custom header with the Native EventSource constructor. But I have achieved it using the EventSource polyfill using this package (npm package)
Two points to note down, if you are using signalR and try to achieve the same as mine.
By default signalR uses native EventSource, but there is a property we can set in the same options parameter in withUrl.
Extend the polyfill constructor and add the custom headers.
import { EventSourcePolyfill } from 'event-source-polyfill';
function EventSourceWithCustomHeader(url, options) {
return new EventSourcePolyfill(url, {
...options,
headers: {
...options.headers,
"custom-header-name": "value"
}
});
}
const conn = new signalR.HubConnectionBuilder()
.withUrl("/chat", {
headers: {
"custom-header-name": "value"
},
EventSource: EventSourceWithCustomHeader,
})
.build();

Sencha Touch ajax prefilter, supply url root for all ajax requests

In a Sencha touch aplication, can I use some sort of ajax prefilter and supply a URL root for all my requests (from stores and models)?
One more thing,
Is there a way to add a AUTH-TOKEN header, alto to every ajax request?
Thank you!
Ext.Ajax.request is a singleton instance. You can tap on beforerequest event and change/modify url, add/remove headers and do all other modification as needed. This way you can capture all your ajax request. You should be able to do this in your app.launch function.
Ext.Ajax.on('beforerequest', function(conn, request, eOpts){
// Your implementaion here. change request.url, request.headers etc
});
If you're setting the URL on your store proxies via Ext.data.proxy.Ajax.url, you could do this in one of two ways.
One: you can extend (or override) the class, adding the logic yourself.
Two: you could configure your URLs as such:
//someplace define this...
var urlPrefix = function() {
return '/path/to/API/';
};
//in your store/proxy
Ext.define('MyApp.store.Foo', {
config : {
proxy : {
type : 'ajax',
url : urlPrefix() + 'API_endpoint'
}
}
});
I've done that a bunch of times before.
As for your Auth headers, you can override Ext.data.Connection to include additional headers. The key here is that you would need to override it very early in the application lifetime, because other classes have unique instances of Ext.data.Connection (for example, Ext.Ajax).
But before giving a recommendation on how to apply the headers globally, I'd need to know more about what you needed to do. There's many things to consider before doing that.

Grails how to post out to someone else's API

I am writing a Grails app, and I want the controller to hit some other API with a POST and then use the response to generate the page my user sees. I am not able to Google the right terms to find anything about posting to another page and receiving the response with Grails. Links to tutorials or answers like "Thats called..." would me much appreciated.
Seems like you are integrating with some sort of RESTful web service. There is REST client plugin, linked here.
Alternatively, its quite easy to do this without a plugin, linked here.
I highly recommend letting your controller just be a controller. Abstract your interface with this outside service into some class like OtherApiService or some sort of utility. Keep all the code that communicates with this outside service in one place; that way you can mock your integration component and make testing everywhere else easy. If you do this as a service, you have room to expand, say in the case you want to start storing some data from the API in your own app.
Anyway, cutting and posting from the linked documentation (the second link), the following shows how to send a GET to an API and how to set up handlers for success and failures, as well as dealing with request headers and query params -- this should have everything you need.
#Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.5.0-RC2' )
import groovyx.net.http.*
import static groovyx.net.http.ContentType.*
import static groovyx.net.http.Method.*
def http = new HTTPBuilder( 'http://ajax.googleapis.com' )
// perform a GET request, expecting JSON response data
http.request( GET, JSON ) {
uri.path = '/ajax/services/search/web'
uri.query = [ v:'1.0', q: 'Calvin and Hobbes' ]
headers.'User-Agent' = 'Mozilla/5.0 Ubuntu/8.10 Firefox/3.0.4'
// response handler for a success response code:
response.success = { resp, json ->
println resp.statusLine
// parse the JSON response object:
json.responseData.results.each {
println " ${it.titleNoFormatting} : ${it.visibleUrl}"
}
}
// handler for any failure status code:
response.failure = { resp ->
println "Unexpected error: ${resp.statusLine.statusCode} : ${resp.statusLine.reasonPhrase}"
}
}
You might also want to check out this, for some nifty tricks. Is has an example with a POST method.

Setting the HTTP Accept header for JsonRestStore

I'm using JsonRestStore but would like to add a custom Accept header to it. What's the best way to go about this?
This is similar to how the dijit.layout.ContentPane allows you to affect the underlying XHR by setting ioArgs. So the question could be "what is JsonRestStore's ioArgs?"
I'm using declarative syntax, but would gladly like to see both methods...
(Please note: I'm not interested in hacking my way around this by modifying the base XHR.)
Your best bet is providing a custom service to JsonRestStore. The easiest way I found of doing this is building the service from dojox.rpc.Rest. In the constructor you can provide a function to create the request arguments for all XHR requests. E.g.
function getRequest(id, args) {
return {
url: '/service/' + id,
handleAs: 'json',
sync: false,
headers: {
Accept: 'your custom header'
}
}
}
var service = new dojo.rpc.Rest('/service/', true /*isJson*/,
undefined /*schema*/, getRequest);
var store = new dojox.data.JsonRestStore({ service: service });
This completely ignores the args parameter that can include sorting and range arguments to your service.
These links will provide more information:
Use Dojo's JsonRestStore with your REST services: IBM developerWorks article with a more advanced and customizable solution
RESTful JSON + Dojo Data: Sitepen post
dojox.rpc.Rest source file (look for service._getRequest)