#Bean
public Server rsServer2() {
JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();
endpoint.setProvider(new JacksonJsonProvider());
endpoint.setBus(cxf());
endpoint.setAddress("/rservice2");
endpoint.setServiceBeans(Arrays.<Object>asList(someService3));
endpoint.setFeatures(Arrays.asList(swagger2Feature2()));
return endpoint.create();
}
#Bean
public Server rsServer() {
JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();
endpoint.setProvider(new JacksonJsonProvider());
endpoint.setBus(cxf());
endpoint.setAddress("/rservice");
endpoint.setServiceBeans(Arrays.<Object>asList(someService2));
endpoint.setFeatures(Arrays.asList(swagger2Feature()));
return endpoint.create();
}
#Bean("swagger2Feature")
Swagger2Feature swagger2Feature() {
System.out.println("Swagger2Feature Called");
Swagger2Feature swagger2Feature = new Swagger2Feature();
swagger2Feature.setResourcePackage("com.service2");
swagger2Feature.setPrettyPrint(true);
return swagger2Feature;
}
#Bean("swagger2Feature2")
Swagger2Feature swagger2Feature2() {
System.out.println("Swagger2Feature2 Called");
Swagger2Feature swagger2Feature2 = new Swagger2Feature();
swagger2Feature2.setResourcePackage("com.service3");
swagger2Feature2.setPrettyPrint(true);
return swagger2Feature2;
}
//
When hitting /basepath/rservice/services in browser. Getting below api listed.
/basepath/rservice2/api-docs?url=/basepath/rservice2/swagger.json
/basepath/rservice/api-docs?url=/basepath/rservice2/swagger.json
Both of the links are giving services listed only in
swagger2Feature.setResourcePackage("com.service2").
Desired result : both links should give their individual api's listed in their corresponding swagger2Feature.setResourcePackage.
Related
ASP.NET 5 MVC Core application serves multiple sites using LigerShark WebOptimizer ( https://github.com/ligershark/WebOptimizer )
https://example.com/store1
https://example.com/store2
https://example2.com
All those sites should served from wwwroot directory containing same files for those urls.
Sites are defined in hosts.json file:
{
"EevaHosts": {
"example.com/store1": {}
"example.com/store2": {}
"example2.com": {}
}
}
I tried code below to force WebOptimizer to use same wwwwroot directory for every site in Debian Linux but got exception for https://example.com/store1/site.js
No files found matching "/store1/js/site.js" exist in
"/var/www/appbasedir/wwwroot/"
How to force web optimizer to use same wwwwroot directory for all sites ?
If Weboptimizer middleware is removed, static files are serverd properly.
In StartUp.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{ ...
var eevakonf = new ConfigurationBuilder().AddJsonFile("hosts.json").Build();
foreach (var host1 in eevakonf.GetSection("EevaHosts").GetChildren())
{
if (!host1.Key.Contains("/"))
app.UseWebOptimizer();
else
app.UseWebOptimizer(env, new FileProviderOptions[] { new FileProviderOptions()
{
// example.com/store1 -> /store1
RequestPath = new PathString(RequestPathExtract(host1)),
FileProvider = env.WebRootFileProvider
}
});
}
// Using single call causes the same exception:
//HashSet<FileProviderOptions> fp = new();
//foreach (var host1 in eevakonf.GetSection("EevaHosts").GetChildren())
//{
// if (host1.Key.Contains("/"))
// fp.Add(new FileProviderOptions() {
// RequestPath = new PathString(RequestPathExtract(host1)) ,
// FileProvider = env.WebRootFileProvider
// });
//}
//app.UseWebOptimizer(env, fp.ToArray());
foreach (var host in eevakonf.GetSection("EevaHosts").GetChildren())
{
if (!host.Key.Contains("/"))
app.UseStaticFiles();
else
{
app.UseStaticFiles(new StaticFileOptions
{
RequestPath = new PathString(RequestPathExtract(host))
});
}
}
}
static string RequestPathExtract(IConfigurationSection host)
{
return "/" + StrExtract(host.Key, "/");
}
static string StrExtract(string cSearchExpression, string cBeginDelim)
{
int nbpos = At(cBeginDelim, cSearchExpression);
return cSearchExpression[(nbpos + cBeginDelim.Length - 1)..];
}
When I run my web api method using Postman passing in my URL, it works fine - it returns the value of '5' which I expect since the call returns just a single integer. Also at the very bottom I include another method of my web api that I run using Postman and it too works just fine.
http://localhost:56224/api/profileandblog/validatelogin/DemoUser1/DemoUser1Password/169.254.102.60/
However, in the client - an Asp.Net MVC method, when building the URL, it is DROPPING the "/api/profileandblog" part. Note: I'm using "attribute routing" in the web api.
Here is the Asp.Net MVC method to call the web api:
I stop it on this line so I can see the error details: if (result1.IsSuccessStatusCode)
It's INCORRECTLY building the URL as: http://localhost:56224/validatelogin/DemoUser1/DemoUser1Password/169.254.102.60/
It's dropping the: "/api/profileandblog" part that should follow 56224.
So it give's me the Not found.
Why does it drop it? It has the localhost:56224 correct.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SignIn(SignInViewModel signInViewModel)
{
int returnedApiValue = 0;
User returnedApiUser = new User();
DateTime currentDateTime = DateTime.Now;
string hostName = Dns.GetHostName();
string myIpAddress = Dns.GetHostEntry(hostName).AddressList[2].ToString();
try
{
if (!this.IsCaptchaValid("Captcha is not valid"))
{
ViewBag.errormessage = "Error: captcha entered is not valid.";
}
else
{
if (!string.IsNullOrEmpty(signInViewModel.Username) && !string.IsNullOrEmpty(signInViewModel.Password))
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:56224/api/profileandblog");
string restOfUrl = "/validatelogin/" + signInViewModel.Username + "/" + signInViewModel.Password + "/" + myIpAddress + "/";
// Call the web api to validate the sign in.
// Sends back a -1(failure), -2(validation issue) or the UserId(success) via an OUTPUT parameter.
var responseTask1 = client.GetAsync(restOfUrl);
responseTask1.Wait();
var result1 = responseTask1.Result;
if (result1.IsSuccessStatusCode)
{
var readTask1 = result1.Content.ReadAsAsync<string>();
readTask1.Wait();
returnedApiValue = Convert.ToInt32(readTask1.Result);
if (returnedApiValue == -2)
{
ViewBag.errormessage = "You entered an invalid user name and/or password";
}
else
{
// I have the 'user id'.
// Continue processing...
}
}
else
{
ModelState.AddModelError(string.Empty, "Server error on signing in. 'validatelogin'. Please contact the administrator.");
}
}
}
}
return View(signInViewModel);
}
catch (Exception)
{
throw;
}
}
Per the suggestion about not having headers, I used another tutorial (https://www.c-sharpcorner.com/article/consuming-asp-net-web-api-rest-service-in-asp-net-mvc-using-http-client/) and it has the code for defining the headers. But it is coded slightly different - using async Task<> on the method definition. I was not using async in my prior version.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SignIn(SignInViewModel signInViewModel)
{
int returnedApiValue = 0;
User returnedApiUser = new User();
DateTime currentDateTime = DateTime.Now;
string hostName = Dns.GetHostName();
string myIpAddress = Dns.GetHostEntry(hostName).AddressList[2].ToString();
try
{
if (!this.IsCaptchaValid("Captcha is not valid"))
{
ViewBag.errormessage = "Error: captcha entered is not valid.";
}
else
{
if (!string.IsNullOrEmpty(signInViewModel.Username) && !string.IsNullOrEmpty(signInViewModel.Password))
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:56224/api/profileandblog");
string restOfUrl = "/validatelogin/" + signInViewModel.Username + "/" + signInViewModel.Password + "/" + myIpAddress + "/";
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Call the web api to validate the sign in.
// Sends back a -1(failure), -2(validation issue) or the UserId(success) via an OUTPUT parameter.
HttpResponseMessage result1 = await client.GetAsync(restOfUrl);
if (result1.IsSuccessStatusCode)
{
var readTask1 = result1.Content.ReadAsAsync<string>();
readTask1.Wait();
returnedApiValue = Convert.ToInt32(readTask1.Result);
if (returnedApiValue == -2)
{
ViewBag.errormessage = "You entered an invalid user name and/or password";
}
else
{
// I have the 'user id'.
// Do other processing....
}
}
else
{
ModelState.AddModelError(string.Empty, "Server error on signing in. 'validatelogin'. Please contact the administrator.");
}
}
}
}
return View(signInViewModel);
}
catch (Exception)
{
throw;
}
}
It now has a header but still NOT building the URL properly as it is not including the "/api/profileandblog" part.
Here is the web api and the method being called:
namespace GbngWebApi2.Controllers
{
[RoutePrefix("api/profileandblog")]
public class WebApi2Controller : ApiController
{
[HttpGet]
[Route("validatelogin/{userName}/{userPassword}/{ipAddress}/")]
public IHttpActionResult ValidateLogin(string userName, string userPassword, string ipAddress)
{
try
{
IHttpActionResult httpActionResult;
HttpResponseMessage httpResponseMessage;
int returnValue = 0;
// Will either be a valid 'user id" or a -2 indicating a validation issue.
returnValue = dataaccesslayer.ValidateLogin(userName, userPassword, ipAddress);
httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK, returnValue);
httpActionResult = ResponseMessage(httpResponseMessage);
return httpActionResult;
}
catch (Exception)
{
throw;
}
}
}
}
Here's the network tab of the client browser before I hit the button to fire of the Asp.Net MVC method.
The network tab of the client browser after I hit the button to fire of the Asp.Net MVC method and it fails.
Here's another example of Postman executing another method of my api just fine.
I got it to work by setting this as: client.BaseAddress = new Uri("localhost:56224"); and setting the string restOfUrl = "/api/profileandblog/validatesignin/" + signInViewModel.Username + "/" + signInViewModel.Password + "/" + myIpAddress + "/";
I have an ASP.NET Core 3.0 Web API endpoint that I have set up to allow me to post large audio files. I have followed the following directions from MS docs to set up the endpoint.
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.0#kestrel-maximum-request-body-size
When an audio file is uploaded to the endpoint, it is streamed to an Azure Blob Storage container.
My code works as expected locally.
When I push it to my production server in Azure App Service on Linux, the code does not work and errors with
Unhandled exception in request pipeline: System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Request body too large.
Per advice from the above article, I have configured incrementally updated Kesterl with the following:
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseKestrel((ctx, options) =>
{
var config = ctx.Configuration;
options.Limits.MaxRequestBodySize = 6000000000;
options.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100,
gracePeriod: TimeSpan.FromSeconds(10));
options.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100,
gracePeriod: TimeSpan.FromSeconds(10));
options.Limits.RequestHeadersTimeout =
TimeSpan.FromMinutes(2);
}).UseStartup<Startup>();
Also configured FormOptions to accept files up to 6000000000
services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 6000000000;
});
And also set up the API controller with the following attributes, per advice from the article
[HttpPost("audio", Name="UploadAudio")]
[DisableFormValueModelBinding]
[GenerateAntiforgeryTokenCookie]
[RequestSizeLimit(6000000000)]
[RequestFormLimits(MultipartBodyLengthLimit = 6000000000)]
Finally, here is the action itself. This giant block of code is not indicative of how I want the code to be written but I have merged it into one method as part of the debugging exercise.
public async Task<IActionResult> Audio()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
throw new ArgumentException("The media file could not be processed.");
}
string mediaId = string.Empty;
string instructorId = string.Empty;
try
{
// process file first
KeyValueAccumulator formAccumulator = new KeyValueAccumulator();
var streamedFileContent = new byte[0];
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit
);
var reader = new MultipartReader(boundary, Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
_permittedExtensions, _fileSizeLimit);
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name).Value;
var encoding = FileHelpers.GetEncoding(section);
if (encoding == null)
{
return BadRequest($"The request could not be processed: Bad Encoding");
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
return BadRequest($"The request could not be processed: Key Count limit exceeded.");
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
var form = formAccumulator;
var file = streamedFileContent;
var results = form.GetResults();
instructorId = results["instructorId"];
string title = results["title"];
string firstName = results["firstName"];
string lastName = results["lastName"];
string durationInMinutes = results["durationInMinutes"];
//mediaId = await AddInstructorAudioMedia(instructorId, firstName, lastName, title, Convert.ToInt32(duration), DateTime.UtcNow, DateTime.UtcNow, file);
string fileExtension = "m4a";
// Generate Container Name - InstructorSpecific
string containerName = $"{firstName[0].ToString().ToLower()}{lastName.ToLower()}-{instructorId}";
string contentType = "audio/mp4";
FileType fileType = FileType.audio;
string authorName = $"{firstName} {lastName}";
string authorShortName = $"{firstName[0]}{lastName}";
string description = $"{authorShortName} - {title}";
long duration = (Convert.ToInt32(durationInMinutes) * 60000);
// Generate new filename
string fileName = $"{firstName[0].ToString().ToLower()}{lastName.ToLower()}-{Guid.NewGuid()}";
DateTime recordingDate = DateTime.UtcNow;
DateTime uploadDate = DateTime.UtcNow;
long blobSize = long.MinValue;
try
{
// Update file properties in storage
Dictionary<string, string> fileProperties = new Dictionary<string, string>();
fileProperties.Add("ContentType", contentType);
// update file metadata in storage
Dictionary<string, string> metadata = new Dictionary<string, string>();
metadata.Add("author", authorShortName);
metadata.Add("tite", title);
metadata.Add("description", description);
metadata.Add("duration", duration.ToString());
metadata.Add("recordingDate", recordingDate.ToString());
metadata.Add("uploadDate", uploadDate.ToString());
var fileNameWExt = $"{fileName}.{fileExtension}";
var blobContainer = await _cloudStorageService.CreateBlob(containerName, fileNameWExt, "audio");
try
{
MemoryStream fileContent = new MemoryStream(streamedFileContent);
fileContent.Position = 0;
using (fileContent)
{
await blobContainer.UploadFromStreamAsync(fileContent);
}
}
catch (StorageException e)
{
if (e.RequestInformation.HttpStatusCode == 403)
{
return BadRequest(e.Message);
}
else
{
return BadRequest(e.Message);
}
}
try
{
foreach (var key in metadata.Keys.ToList())
{
blobContainer.Metadata.Add(key, metadata[key]);
}
await blobContainer.SetMetadataAsync();
}
catch (StorageException e)
{
return BadRequest(e.Message);
}
blobSize = await StorageUtils.GetBlobSize(blobContainer);
}
catch (StorageException e)
{
return BadRequest(e.Message);
}
Media media = Media.Create(string.Empty, instructorId, authorName, fileName, fileType, fileExtension, recordingDate, uploadDate, ContentDetails.Create(title, description, duration, blobSize, 0, new List<string>()), StateDetails.Create(StatusType.STAGED, DateTime.MinValue, DateTime.UtcNow, DateTime.MaxValue), Manifest.Create(new Dictionary<string, string>()));
// upload to MongoDB
if (media != null)
{
var mapper = new Mapper(_mapperConfiguration);
var dao = mapper.Map<ContentDAO>(media);
try
{
await _db.Content.InsertOneAsync(dao);
}
catch (Exception)
{
mediaId = string.Empty;
}
mediaId = dao.Id.ToString();
}
else
{
// metadata wasn't stored, remove blob
await _cloudStorageService.DeleteBlob(containerName, fileName, "audio");
return BadRequest($"An issue occurred during media upload: rolling back storage change");
}
if (string.IsNullOrEmpty(mediaId))
{
return BadRequest($"Could not add instructor media");
}
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
var result = new { MediaId = mediaId, InstructorId = instructorId };
return Ok(result);
}
I reiterate, this all works great locally. I do not run it in IISExpress, I run it as a console app.
I submit large audio files via my SPA app and Postman and it works perfectly.
I am deploying this code to an Azure App Service on Linux (as a Basic B1).
Since the code works in my local development environment, I am at a loss of what my next steps are. I have refactored this code a few times but I suspect that it's environment related.
I cannot find anywhere that mentions that the level of App Service Plan is the culprit so before I go out spending more money I wanted to see if anyone here had encountered this challenge and could provide advice.
UPDATE: I attempted upgrading to a Production App Service Plan to see if there was an undocumented gate for incoming traffic. Upgrading didn't work either.
Thanks in advance.
-A
Currently, as of 11/2019, there is a limitation with the Azure App Service for Linux. It's CORS functionality is enabled by default and cannot be disabled AND it has a file size limitation that doesn't appear to get overridden by any of the published Kestrel configurations. The solution is to move the Web API app to a Azure App Service for Windows and it works as expected.
I am sure there is some way to get around it if you know the magic combination of configurations, server settings, and CLI commands but I need to move on with development.
I am trying to insert a new entry in my database using web api. I have two web projects: one is a UI project where all the user interaction will occur and the other is a services project which will handle all interactions with my database.
Below is my post method that will take in form data for creating a new team.
// POST: Api/Team/Create
[HttpPost]
public ActionResult Create(Team team)
{
try
{
if (ModelState.IsValid)
{
HttpEndPointContext httpEndPoint = new HttpEndPointContext()
{
AuthenticationMethod = HttpAuthenticationMethods.None,
Ssl = false,
HttpMethod = HttpMethod.Post,
Path = "localhost:32173/api/team/",
QueryStrings = null,
PayloadData = SerializationHelper.Current.Serialize(team.ToString(), SerializationTypes.Xml)
};
IProcessResult result = HttpConnectionManager.Current.SendMessage(httpEndPoint);
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
And this is my method for dealing with my PayloadStream/PayloadData attribute in the above method:
private void StreamPayload(HttpWebRequest webRequest, HttpEndPointContext httpEndPointContext)
{
if (httpEndPointContext.HttpMethod == new HttpMethod("GET"))
return;
//TODO: FIX MAYBE .... sometimes we want to post body with GET.
//Stream vs string
if (httpEndPointContext.PayloadStream == null)
{
//Wrap with SOAP Envelope and method if defined in SoapDefinition
string data = httpEndPointContext.PayloadData ?? String.Empty;
if (httpEndPointContext.SoapDefinition != null)
{
//If parameters is set, clear existing payload data.
data = String.Empty;
if (httpEndPointContext.SoapDefinition.Parameters != null)
foreach (var parameter in httpEndPointContext.SoapDefinition.Parameters)
{
data += String.Format("<{0}>{1}</{0}>", parameter.Key, parameter.Value);
}
data = String.Format("<s:Envelope xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>" +
"<s:Body><{0} xmlns='{2}'>" +
"{1}</{0}></s:Body></s:Envelope>",
httpEndPointContext.SoapDefinition.SoapMethod, data,httpEndPointContext.SoapDefinition.SoapGlobalKey);
}
byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(data);
httpEndPointContext.PayloadStream = new MemoryStream(byteArray);
}
using (Stream requestStream = webRequest.GetRequestStream())
{
StreamHelper.Current.CopyStreams(httpEndPointContext.PayloadStream, requestStream);
requestStream.Close();
}
}
And the code for getting the server response. I'm currently getting an Internal Server (500) Error. Not sure why.
public IProcessResult SendMessage(HttpEndPointContext httpEndPointContext)
{
HttpWebRequest webRequest = CreateWebRequest(httpEndPointContext);
StreamPayload(webRequest, httpEndPointContext);
IProcessResult result = GetWebResponse(webRequest, httpEndPointContext);
return result;
}
private IProcessResult GetWebResponse(HttpWebRequest webRequest, HttpEndPointContext httpEndPointContext)
{
//Get Response
WebResponse response;
IProcessResult result = new ProcessResult(Statuses.Success);
try
{
response = webRequest.GetResponse();
}
catch (System.Net.WebException ex)
{
//Do exception handling. Still get the response for 500s etc.
result.Error.Exception = ex;
result.Status = Constants.Statuses.FailedUnknown;
result.ResponseCodeDescription = ex.Status.ToString();
result.ResponseCode = ex.Status.ToString();
result.Error.ErrorCode = ex.Status.ToString();
response = ex.Response;
//The error did not have any response, such as DNS lookup.
if (response == null)
return result;
}
try
{
//Get the response stream.
Stream responseData = response.GetResponseStream();
if (responseData == null)
throw new CoreException("No Response Data in GetWebResponse.",
"No Response Data in GetWebResponse. EndPoint:{0}", httpEndPointContext.ToString());
// Open the stream using a StreamReader for easy access.
var reader = new StreamReader(responseData);
// Read the content.
result.ResponseData = reader.ReadToEnd();
}
finally
{
response.Close();
}
result.ResponseCode = ((int)((HttpWebResponse)response).StatusCode).ToString();
result.ResponseCodeDescription = ((HttpWebResponse) response).StatusDescription;
return result;
}
And finally, my method for inserting to the database, found in my services project:
//POST api/controller/5
public IProcessResult Insert(Team team)
{
return TeamBusinessManager.Current.Insert(SecurityManager.Current.ConnectionContext, new Team());
}
I'm confused as to why I'm getting the 500 error. I'm not sure if it's the PayloadData attribute in my POST method or is it something wrong with my method in my services project.
I am new to Web API and HTTP.
I am using the MVC 6 (beta version). I have a proxy service (Web API) which has a POST method to get response from another service with XML content returned. I need to return the response content to the client since the client can't call the service directly.
// In my proxy service
public HttpResponseMessage Post(String content)
{
using ( HttpClient client = new HttpClient() ) {
.......
HttpResponseMessage response = client.PostAsync(uri, content).Result;
// I get everything I need in the "response".
// How to return the response or it body to the client.
// return response;
}
}
II need to return the "response" to the client with no or minimum changes. I tried "return response", or create a new HttpResponseMessage, but I only got something like
{"Headers":[{"Key":"Content-Type","Value":["text/xml"]}]}
in the body.
So is there a simple way to pass the response back to the client? Thanks.
The ASP.NET team is currently working on a "proxy middleware" that does exactly what you're looking for: https://github.com/aspnet/Proxy
Here's how it works internally:
public async Task Invoke(HttpContext context)
{
var requestMessage = new HttpRequestMessage();
if (string.Equals(context.Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
{
var streamContent = new StreamContent(context.Request.Body);
requestMessage.Content = streamContent;
}
// Copy the request headers
foreach (var header in context.Request.Headers)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
requestMessage.Headers.Host = _options.Host + ":" + _options.Port;
var uriString = $"{_options.Scheme}://{_options.Host}:{_options.Port}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}";
requestMessage.RequestUri = new Uri(uriString);
requestMessage.Method = new HttpMethod(context.Request.Method);
using (var responseMessage = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
{
context.Response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
context.Response.Headers.SetValues(header.Key, header.Value.ToArray());
}
foreach (var header in responseMessage.Content.Headers)
{
context.Response.Headers.SetValues(header.Key, header.Value.ToArray());
}
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
context.Response.Headers.Remove("transfer-encoding");
await responseMessage.Content.CopyToAsync(context.Response.Body);
}
}
https://github.com/aspnet/Proxy/blob/dev/src/Microsoft.AspNet.Proxy/ProxyMiddleware.cs