Azure Function Post C# not executing with big body - c#

I'm currently implementing an Azure Function App that exposes a few Functions (mostly gets).
The following code seems to have an issue:
using System;
using System.Collections.Specialized;
using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using InternalVacanciesAzureFunction.Model;
using Microsoft.Extensions.Options;
using System.Text.Json;
using System.Collections.Generic;
namespace IVAFunction
{
public class PostFunction
{
private readonly HttpClient _httpClient;
public PostFunction(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient();
}
[Function("postFunction")]
public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", "put")] HttpRequestData req,
FunctionContext executionContext)
{
ILogger logger = executionContext.GetLogger("PostFunction");
logger.LogError("Code hit: PostFunction.cs");
HttpResponseData response = req.CreateResponse();
string body = new System.IO.StreamReader(req.Body).ReadToEnd();
JsonResponse data = Nfunction.postFunction(_httpClient, "/PostFunction", body, logger, requestPrincipalName);
if (data.responseType.Equals(ResponseType.OK))
{
response.StatusCode = HttpStatusCode.OK;
}
else
{
response.StatusCode = HttpStatusCode.InternalServerError;
}
response.Headers.Add("Content-Type", "application/json; charset=utf-8");
response.WriteString(data.json);
return response;
}
}
}
The data that is posted to this function is a JSON including a BASE64 encoded string in two of the fields. The max size for each of those two fields is 1.5MB. Everytime I post something small e.g. 2 x 400B, everything goes fine. But when I sent something like 2 x 900kB the logging show up like this:
2021-12-07T07:42:28.455 [Debug] Request successfully matched the route with name 'postFunction' and template 'api/postFunction'
2021-12-07T07:42:29.129 [Information] Executing 'Functions.postFunction' (Reason='This function was programmatically called via the host APIs.', Id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
The "Code hit" logger code is never hit and after a while the function times out.
Anyone having a clue what is going on? I can reproduce the issue on both my local dev environment as well on actual Azure.

You got a lot of missing directives or assembly references there
except for that the last } is unnenecessary.

Related

Blazor Server: Force file download

In the context of the development of my website MySpector.com developed with Blazor Server,
I want to give the user the possibility to download files which are stored on the disk of the machine.
To do this I want to create a Blazor Component / Page ( I do not know what) which will:
take as input a database ID,
fetch the database to locate the file in disk
dynamically transfer the data to be downloaded to the webbrowser
I am having problem to understand how I can program the http response headers and content with Blazor.
I have seen a potential solution on Blazor download on microsoft but I do not like the idea to create a RAZOR page inside a BLAZOR solution, as this introduce a component organized in a different way than the other ( 2 files: .cshtml + .cshtml.cs, instead of 1 .razor file)
see below Downloader.cshtml in the file hierarchy which looks like different than the other pages.
I am also aware of the 'different layers' of blazor server where a page is only a subpart of a bigger html context so I see the problem, but I woudl like to find a easy way only to override http GET via a .razor single file... if this is possible.
Regards.
You can get control of the http response with headers by using a Asp net core Controller. This is compatible with Blazor server technology.
To do so, you can folow up the 2 links below:
Mvc controller added to blazor server
Return file from Mvc Controller
I added a controller directory with the downloader controller:
I patch the code of startup with this diff:
Then I add the code for the downloader controller:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Mime;
using System.Web;
using NLog;
using MySpector.Core;
using MySpector.Objects;
//https://chanmingman.wordpress.com/2020/07/12/add-web-api-controller-to-blazor-project-asp-net/
//https://stackoverflow.com/questions/5826649/returning-a-file-to-view-download-in-asp-net-mvc
namespace FrontEnd.Pages
{
[Route("api/[controller]")]
[ApiController]
public class DownloadController : Controller
{
private static Logger _log = LogManager.GetCurrentClassLogger();
private ResultStorage _resultStorage;
// GET /api/Download/3
[HttpGet("{resultDbId:int}")]
public ActionResult Get(int resultDbId)
{
bool isConnected = ServiceLocator.Instance.Repo.Connect();
if (!isConnected)
{
_log.Error("Cannot connect");
_log.Error("Returning Http 404");
return NotFound();
}
else
{
_log.Debug("Connected to db");
_log.Debug("Input: ResultDbId=" + resultDbId);
_resultStorage = ServiceLocator.Instance.Repo.GetSingleResult(resultDbId);
}
if(_resultStorage==null)
{
_log.Error("Returning Http 404");
return NotFound();
}
string filename = _resultStorage.File.FilePath;
var cd = new ContentDisposition
{
FileName = filename,
Inline = true,
};
Response.Headers.Add("Content-Disposition", cd.ToString());
byte[] filedata = System.IO.File.ReadAllBytes(filename);
string contentType = "application/octet-stream";
return File(filedata, contentType);
}
}
}
You can then call this module with:
string downloadUri = "api/Download/" + #_report.Result.DbId;
<NavLink href="#downloadUri">Download raw file</NavLink><br />

Why is my header data missing from my Azure Function Http Trigger in .Net 5 when calling from HttpClient.GetAsync

I have a client using HttpClient.GetAsync to call into a Azure Function Http Trigger in .Net 5.
When I call the function using PostMan, I get my custom header data.
However, when I try to access my response object (HttpResponseMessage) that is returned from HttpClient.GetAsync, my header data empty.
I have my Content data and my Status Code. But my custom header data are missing.
Any insight would be appreciated since I have looking at this for hours.
Thanks for you help.
Edit: Here is the code where I am making the http call:
public async Task<HttpResponseMessage> GetQuotesAsync(int? pageNo, int? pageSize, string searchText)
{
var requestUri = $"{RequestUri.Quotes}?pageNo={pageNo}&pageSize={pageSize}&searchText={searchText}";
return await _httpClient.GetAsync(requestUri);
}
Edit 8/8/2021: See my comment below. The issue has something to do with using Blazor Wasm Client.
For anyone having problems after following the tips on this page, go back and check the configuration in the host.json file. you need the Access-Control-Expose-Headers set to * or they won't be send even if you add them. Note: I added the "extensions" node below and removed my logging settings for clarity.
host.json (sample file):
{
"version": "2.0",
"extensions": {
"http": {
"customHeaders": {
"Access-Control-Expose-Headers": "*"
}
}
}
}
This is because HttpResponseMessage's Headers property data type is HttpResponseHeaders but HttpResponseData's Headers property data type is HttpHeadersCollection. Since, they are different, HttpResponseHeaders could not bind to HttpHeadersCollection while calling HttpClient.GetAsync(as it returns HttpResponseMessage).
I could not find a way to read HttpHeadersCollection through HttpClient.
As long as your Azure function code is emitting the header value, you should be able to read that in your client code from the Headers collection of HttpResponseMessage. Nothing in your azure function (which is your remote endpoint you are calling) makes it any different. Remember, your client code has no idea how your remote endpoint is implemented. Today it is azure functions, tomorrow it may be a full blown aspnet core web api or a REST endpoint written in Node.js. Your client code does not care. All it cares is whether the Http response it received has your expected header.
Asumming you have an azure function like this where you are adding a header called total-count to the response.
[Function("quotes")]
public static async Task<HttpResponseData> RunAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("Quotes");
logger.LogInformation("C# HTTP trigger function processed a request for Quotes.");
var quotes = new List<Quote>
{
new Quote { Text = "Hello", ViewCount = 100},
new Quote { Text = "Azure Functions", ViewCount = 200}
};
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("total-count", quotes.Count.ToString());
await response.WriteAsJsonAsync(quotes);
return response;
}
Your existing client code should work as long as you read the Headers property.
public static async Task<HttpResponseMessage> GetQuotesAsync()
{
var requestUri = "https://shkr-playground.azurewebsites.net/api/quotes";
return await _httpClient.GetAsync(requestUri);
}
Now your GetQuotesAsync method can be called somewhere else where you will use the return value of it (HttpResponseMessage instance) and read the headers. In the below example, I am reading that value and adding to a string variable. HttpResponseMessage implements IDisposable. So I am using a using construct to implicitly call the Dispose method.
var msg = "Total count from response headers:";
using (var httpResponseMsg = await GetQuotesAsync())
{
if (httpResponseMsg.Headers.TryGetValues("total-count", out var values))
{
msg += values.FirstOrDefault();
}
}
// TODO: use "msg" variable as needed.
The types which Azure function uses for dealing with response headers is more of an implementation concern of azure functions. It has no impact on your client code where you are using HttpClient and HttpResponseMessage. Your client code is simply dealing with standard http call response (response headers and body)
The issue is not with Blazor WASM, rather if that header has been exposed on your API Side. In your azure function, add the following -
Note: Postman will still show the headers even if you don't expose the headers like below. That's because, Postman doesn't care about CORS headers. CORS is just a browser concept and not a strong security mechanism. It allows you to restrict which other web apps may use your backend resources and that's all.
First create a Startup File to inject the HttpContextAccessor
Package Required: Microsoft.Azure.Functions.Extensions
[assembly: FunctionsStartup(typeof(FuncAppName.Startup))]
namespace FuncAppName
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddScoped<HttpContextAccessor>();
}
}
}
Next, inject it into your main Function -
using Microsoft.AspNetCore.Http;
namespace FuncAppName
{
public class SomeFunction
{
private readonly HttpContext _httpContext;
public SomeFunction(HttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
[FunctionName("SomeFunc")]
public override Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, new[] { "post" }, Route = "run")] HttpRequest req)
{
var response = "Some Response"
_httpContext.Response.Headers.Add("my-custom-header", "some-custom-value");
_httpContext.Response.Headers.Add("my-other-header", "some-other-value");
_httpContext.Response.Headers.Add("Access-Control-Expose-Headers", "my-custom-header, my-other-header");
return new OkObjectResult(response)
}
If you want to allow all headers you can use wildcard (I think, not tested) -
_httpContext.Response.Headers.Add("Access-Control-Expose-Headers", "*");
You still need to add your web-app url to the azure platform CORS. You can add * wildcard, more info here - https://iotespresso.com/allowing-all-cross-origin-requests-azure-functions/
to enable CORS for Local Apps during development - https://stackoverflow.com/a/60109518/9276081
Now to access those headers in your Blazor WASM, as an e.g. you can -
protected override async Task OnInitializedAsync()
{
var content = JsonContent.Create(new { query = "" });
using (var client = new HttpClient())
{
var result = await client.PostAsync("https://func-app-name.azurewebsites.net/api/run", content);
var headers = result.Headers.ToList();
}
}

Is there a way to return data from the called azure function back to logic app who called it?

I have office 365 trigger "when new e-mail arrives?"
i initialize a variable username with value Max Sample
Then called azure function FxNet21HttpTrigger1
and if determine there a username for the Logic App is this possible to chnge it there give another Variable back
check the the username and do one thing if it is "Donald Duck" or another thing if not
I'm searching for a minimal way to set value in the azure function and react on the value in the logic app.
Logic App Designer Screenshot
This is using Version 1 Azure functions (version 2 and 3 are similar):
using System.Net;
using System.Net.Http;
....
[FunctionName("MyFunction")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
return new GetHtmlResponseContent("<html><body>Hello <b>world</b></body></html>"};
}
private static HttpResponseMessage GetHtmlResponseContent(string msg, HttpStatusCode httpStatusCode)
{
var response = new HttpResponseMessage(httpStatusCode);
response.Content = new StringContent(msg));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
return response;
}
You might want to return a JSON payload containing your return value(s).
How do I return JSON from an Azure Function

How to connect to EventHubs with Apache-Kafka extension using Azure Functions?

Can I use SharedAccessKey to connect to the broker (EventHubs)?
I'm unable to connect to my Azure EventHubs.
We use SharedAccessKey instead of SSL to get connected and I have this configuration to do it.
"EventBusConfig": {
"BootstrapServers": "anyname.servicebus.windows.net:9093",
"SecurityProtocol": "SaslSsl",
"SaslMechanism": "Plain",
"SaslUsername": "$ConnectionString",
"SaslPassword":
"Endpoint=sb://anyname.servicebus.windows.net/;SharedAccessKeyName=anyname.;SharedAccessKey=CtDbJ/Kfjs749
8s--anypassword--SkSk749/z2Z5Fr9///33/qQ+R6Cyg=",
"SocketTimeoutMs": "60000",
"SessionTimeoutMs": "30000",
"GroupId": "NameOfTheGroup",
"AutoOffsetReset": "Earliest",
"BrokerVersionFallback": "1.0.0",
"Debug": "cgrp"
}
But it seems I need the certification path (the pem file)
I want to produce a simple message like this
I'm using https://github.com/Azure/azure-functions-kafka-extension but I don't know if this beta library can handle SharedAccessKey.
I got this error when trying to connect:
Any help will be appreciated
I was able to produce and consume messages using the extension "https://github.com/Azure/azure-functions-kafka-extension".
To consume a message was easy because of the property "EventHubConnectionString" very intuitive.
To produce a message you need to configure a CA certificate, I thought that I need this from Azure but I was wrong and I just followed these instructions to make it work.
Download and set the CA certification location. As described in Confluent documentation, the .NET library does not have the capability to access root CA certificates.
Missing this step will cause your function to raise the error "sasl_ssl://xyz-xyzxzy.westeurope.azure.confluent.cloud:9092/bootstrap: Failed to verify broker certificate: unable to get local issuer certificate (after 135ms in state CONNECT)"
To overcome this, we need to:
Download CA certificate (i.e. from https://curl.haxx.se/ca/cacert.pem).
Rename the certificate file to anything other than cacert.pem to avoid any conflict with existing EventHubs Kafka certificate that is part of the extension.
Include the file in the project, setting "copy to output directory"
Set the SslCaLocation trigger attribute property. In the example, we set to confluent_cloud_cacert.pem
This is my producer Azure function with Kafka binding
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.Azure.WebJobs.Extensions.Kafka;
namespace EY.Disruptor.AzureFunctionsWithKafka
{
public static class Function
{
[FunctionName("Producer")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[Kafka("BootstrapServer",
"topic.event",
Username = "ConfluentCloudUsername",
Password = "ConfluentCloudPassword",
SslCaLocation = "confluent_cloud_cacert.pem",
AuthenticationMode = BrokerAuthenticationMode.Plain,
Protocol = BrokerProtocol.SaslSsl
)] IAsyncCollector<KafkaEventData<string>> events,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name ??= data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
var kafkaEvent = new KafkaEventData<string>()
{
Value = name
};
await events.AddAsync(kafkaEvent);
return new OkObjectResult(responseMessage);
}
}
}
This is my consume Azure function with Kafka binding
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Kafka;
using Microsoft.Extensions.Logging;
namespace EY.Disruptor.AzureFunctionsWithKafka
{
public static class Consumer
{
[FunctionName("FunctionKafkaConsumer")]
public static void Run(
[KafkaTrigger("BootstrapServer",
"topic.event",
Username = "ConfluentCloudUsername",
Password = "ConfluentCloudPassword",
EventHubConnectionString = "ConfluentCloudPassword",
AuthenticationMode = BrokerAuthenticationMode.Plain,
Protocol = BrokerProtocol.SaslSsl,
ConsumerGroup = "Group1")] KafkaEventData<string>[] kafkaEvents,
ILogger logger)
{
foreach (var kafkaEvent in kafkaEvents)
{
logger.LogInformation(kafkaEvent.Value);
}
}
}
}
This is my local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"BootstrapServer": "zyxabc.servicebus.windows.net:9093",
"ConfluentCloudUsername": "$ConnectionString",
"ConfluentCloudPassword": "Endpoint=sb://zyxabc.servicebus.windows.net/;SharedAccessKeyName=TestSvc;SharedAccessKey=YAr/="
}
}
And of course the initialization in the Startup.cs
public void Configure(IWebJobsBuilder builder)
{
builder.AddKafka();
}
I hope this recommendation helps other people :)

Returning an HttpResponseMessage contents from a pass through API in ASP.NET Core [duplicate]

I'm developing an ASP.Net Core web application where I need to create a kind of "authentication proxy" to another (external) web service.
What I mean by authentication proxy is that I will receive requests through a specific path of my web app and will have to check the headers of those requests for an authentication token that I'll have issued earlier, and then redirect all the requests with the same request string / content to an external web API which my app will authenticate with through HTTP Basic auth.
Here's the whole process in pseudo-code
Client requests a token by making a POST to a unique URL that I sent him earlier
My app sends him a unique token in response to this POST
Client makes a GET request to a specific URL of my app, say /extapi and adds the auth-token in the HTTP header
My app gets the request, checks that the auth-token is present and valid
My app does the same request to the external web API and authenticates the request using BASIC authentication
My app receives the result from the request and sends it back to the client
Here's what I have for now. It seems to be working fine, but I'm wondering if it's really the way this should be done or if there isn't a more elegant or better solution to this? Could that solution create issues in the long run for scaling the application?
[HttpGet]
public async Task GetStatement()
{
//TODO check for token presence and reject if issue
var queryString = Request.QueryString;
var response = await _httpClient.GetAsync(queryString.Value);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
[HttpPost]
public async Task PostStatement()
{
using (var streamContent = new StreamContent(Request.Body))
{
//TODO check for token presence and reject if issue
var response = await _httpClient.PostAsync(string.Empty, streamContent);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType?.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
}
_httpClient being a HttpClient class instantiated somewhere else and being a singleton and with a BaseAddressof http://someexternalapp.com/api/
Also, is there a simpler approach for the token creation / token check than doing it manually?
If anyone is interested, I took the Microsoft.AspNetCore.Proxy code and made it a little better with middleware.
Check it out here: https://github.com/twitchax/AspNetCore.Proxy. NuGet here: https://www.nuget.org/packages/AspNetCore.Proxy/. Microsoft archived the other one mentioned in this post, and I plan on responding to any issues on this project.
Basically, it makes reverse proxying another web server a lot easier by allowing you to use attributes on methods that take a route with args and compute the proxied address.
[ProxyRoute("api/searchgoogle/{query}")]
public static Task<string> SearchGoogleProxy(string query)
{
// Get the proxied address.
return Task.FromResult($"https://www.google.com/search?q={query}");
}
I ended up implementing a proxy middleware inspired by a project in Asp.Net's GitHub.
It basically implements a middleware that reads the request received, creates a copy from it and sends it back to a configured service, reads the response from the service and sends it back to the caller.
This post talks about writing a simple HTTP proxy logic in C# or ASP.NET Core. And allowing your project to proxy the request to any other URL. It is not about deploying a proxy server for your ASP.NET Core project.
Add the following code anywhere of your project.
public static HttpRequestMessage CreateProxyHttpRequest(this HttpContext context, Uri uri)
{
var request = context.Request;
var requestMessage = new HttpRequestMessage();
var requestMethod = request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(request.Body);
requestMessage.Content = streamContent;
}
// Copy the request headers
foreach (var header in request.Headers)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
requestMessage.Headers.Host = uri.Authority;
requestMessage.RequestUri = uri;
requestMessage.Method = new HttpMethod(request.Method);
return requestMessage;
}
This method covert user sends HttpContext.Request to a reusable HttpRequestMessage. So you can send this message to the target server.
After your target server response, you need to copy the responded HttpResponseMessage to the HttpContext.Response so the user's browser just gets it.
public static async Task CopyProxyHttpResponse(this HttpContext context, HttpResponseMessage responseMessage)
{
if (responseMessage == null)
{
throw new ArgumentNullException(nameof(responseMessage));
}
var response = context.Response;
response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in responseMessage.Content.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
response.Headers.Remove("transfer-encoding");
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(response.Body, _streamCopyBufferSize, context.RequestAborted);
}
}
And now the preparation is complete. Back to our controller:
private readonly HttpClient _client;
public YourController()
{
_client = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = false
});
}
public async Task<IActionResult> Rewrite()
{
var request = HttpContext.CreateProxyHttpRequest(new Uri("https://www.google.com"));
var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
await HttpContext.CopyProxyHttpResponse(response);
return new EmptyResult();
}
And try to access it. It will be proxied to google.com
A nice reverse proxy middleware implementation can also be found here: https://auth0.com/blog/building-a-reverse-proxy-in-dot-net-core/
Note that I replaced this line here
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
with
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToString());
Original headers (e.g. like an authorization header with a bearer token) would not be added without my modification in my case.
I had luck using twitchax's AspNetCore.Proxy NuGet package, but could not get it to work using the ProxyRoute method shown in twitchax's answer. (Could have easily been a mistake on my end.)
Instead I defined the mapping in Statup.cs Configure() method similar to the code below.
app.UseProxy("api/someexternalapp-proxy/{arg1}", async (args) =>
{
string url = "https://someexternalapp.com/" + args["arg1"];
return await Task.FromResult<string>(url);
});
Piggy-backing on James Lawruk's answer https://stackoverflow.com/a/54149906/6596451 to get the twitchax Proxy attribute to work, I was also getting a 404 error until I specified the full route in the ProxyRoute attribute. I had my static route in a separate controller and the relative path from Controller's route was not working.
This worked:
public class ProxyController : Controller
{
[ProxyRoute("api/Proxy/{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
This does not:
[Route("api/[controller]")]
public class ProxyController : Controller
{
[ProxyRoute("{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
Hope this helps someone!
Twitchax's answer seems to be the best solution at the moment. In researching this, I found that Microsoft is developing a more robust solution that fits the exact problem the OP was trying to solve.
Repo: https://github.com/microsoft/reverse-proxy
Article for Preview 1 (they actually just released prev 2): https://devblogs.microsoft.com/dotnet/introducing-yarp-preview-1/
From the Article...
YARP is a project to create a reverse proxy server. It started when we noticed a pattern of questions from internal teams at Microsoft who were either building a reverse proxy for their service or had been asking about APIs and technology for building one, so we decided to get them all together to work on a common solution, which has become YARP.
YARP is a reverse proxy toolkit for building fast proxy servers in .NET using the infrastructure from ASP.NET and .NET. The key differentiator for YARP is that it is being designed to be easily customized and tweaked to match the specific needs of each deployment scenario. YARP plugs into the ASP.NET pipeline for handling incoming requests, and then has its own sub-pipeline for performing the steps to proxy the requests to backend servers. Customers can add additional modules, or replace stock modules as needed.
...
YARP works with either .NET Core 3.1 or .NET 5 preview 4 (or later). Download the preview 4 (or greater) of .NET 5 SDK from https://dotnet.microsoft.com/download/dotnet/5.0
More specifically, one of their sample apps implements authentication (as for the OP's original intent)
https://github.com/microsoft/reverse-proxy/blob/master/samples/ReverseProxy.Auth.Sample/Startup.cs
Here is a basic implementation of Proxy library for ASP.NET Core:
This does not implement the authorization but could be useful to someone looking for a simple reverse proxy with ASP.NET Core. We only use this for development stages.
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Sample.Proxy
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(options =>
{
options.AddDebug();
options.AddConsole(console =>
{
console.IncludeScopes = true;
});
});
services.AddProxy(options =>
{
options.MessageHandler = new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = true
};
options.PrepareRequest = (originalRequest, message) =>
{
var host = GetHeaderValue(originalRequest, "X-Forwarded-Host") ?? originalRequest.Host.Host;
var port = GetHeaderValue(originalRequest, "X-Forwarded-Port") ?? originalRequest.Host.Port.Value.ToString(CultureInfo.InvariantCulture);
var prefix = GetHeaderValue(originalRequest, "X-Forwarded-Prefix") ?? originalRequest.PathBase;
message.Headers.Add("X-Forwarded-Host", host);
if (!string.IsNullOrWhiteSpace(port)) message.Headers.Add("X-Forwarded-Port", port);
if (!string.IsNullOrWhiteSpace(prefix)) message.Headers.Add("X-Forwarded-Prefix", prefix);
return Task.FromResult(0);
};
});
}
private static string GetHeaderValue(HttpRequest request, string headerName)
{
return request.Headers.TryGetValue(headerName, out StringValues list) ? list.FirstOrDefault() : null;
}
public void Configure(IApplicationBuilder app)
{
app.UseWebSockets()
.Map("/api", api => api.RunProxy(new Uri("http://localhost:8833")))
.Map("/image", api => api.RunProxy(new Uri("http://localhost:8844")))
.Map("/admin", api => api.RunProxy(new Uri("http://localhost:8822")))
.RunProxy(new Uri("http://localhost:8811"));
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

Categories