ServiceStack - As passthru to another ServiceStack service - c#

I currently have an ServiceStack Service that does nothing but relay requests to an internal ServiceStack service.
The relay service is setup something like this (code made brief as an example):
public class RelayService : Service
{
public SomeDTO Get(FetchSomething request)
{
try
{
return new JsonServiceClient(settings.OtherServiceURL).Get(request);
}
catch (Exception)
{
throw;
}
}
public void Put(PersistSomething request)
{
try
{
new JsonServiceClient(settings.OtherServiceURL).Put(request);
}
catch (Exception)
{
throw;
}
}
}
My questions are:
Is it best practice to new up a JsonServiceClient for each request? Or should I inject an instance?
Since the relay service can contain variations on Put/Get that return DTO's or void, is there a cleaner way to relay all calls to the backing ServiceStack service instead of having to duplicate each method in the relay service? Is it possible to do this all in one or a few methods using Any()?
Thanks for any input.

This previous answer for an example of a generic reverse proxy in ServiceStack.
The simplest and most generic approach in ServiceStack would be to register a RawHttpHandler that just forwards the Request to the downstream server and writes the Response to the Output Stream, e.g:
RawHttpHandlers.Add(_ => new CustomActionHandler((req, res) =>
{
var bytes = req.InputStream.ReadFully();
var proxyUrl = settings.OtherServiceURL.CombineWith(req.RawUrl);
var responseBytes = proxyUrl.SendBytesToUrl(method: req.Verb,
requestBody: bytes,
accept:MimeTypes.Json,
contentType: req.ContentType,
responseFilter: webRes =>
{
res.StatusCode = (int)webRes.StatusCode;
res.StatusDescription = webRes.StatusDescription;
res.ContentType = webRes.ContentType;
});
res.OutputStream.Write(responseBytes, 0, responseBytes.Length);
}));
In order to access the RequestStream you'll also want to tell ServiceStack to not inspect the FormData when creating the Request (as this forces reading the request body), which you can skip with:
SetConfig(new HostConfig {
SkipFormDataInCreatingRequest = true
});
Another approach would be to configure something like IIS Application Request Routing and URL Rewriting to use as a reverse proxy.

Related

JsonServiceClient not respecting RedirectHttpHandler response

We have a global handler setup for catching a specific type of exception. It is possibly thrown from multiple service endpoints using a base service implementation. We bind the error handlers and try redirect using a RedirectHttpHandler:
ServiceExceptionHandlers.Add(HandledErrorLogging);
...
private object HandledErrorLogging(IRequest httpreq, object request, Exception ex)
{
if (ex is NoActiveSubscriptionException)
{
return new RedirectHttpHandler
{
RelativeUrl = "/account?error=",
StatusCode = HttpStatusCode.TemporaryRedirect
};
}
}
We are using JsonServiceClient to query these endpoints.
The JsonServiceClient is not respecting the RedirectHttpHandler redirect. When we connect jsonclient.responsefilter(r), r.redirectedicted is false:
let client = new JsonServiceClient(process.env.REACT_APP_API_BASE_URL);
client.setBearerToken(cookie.load("XSRF-TOKEN"));
JsonServiceClient.globalResponseFilter = function(e)
{
console.log("e.redirect:", e.redirected));
};
return client;
}
What is the best way to cause a redirect using the ServiceExceptionHandlers and the JsonServiceClient ?
RedirectHttpHandler is an IHttpAsyncHandler, it can only be used at the start of the request pipeline in RawHttpHandlers which is used to tell ServiceStack which HttpHandler it should use to handle the request.
ServiceExceptionHandlers is used to override handling of an Exception which you can override to return a different error Response DTO.
If nothing has been written to the Response you can return a redirect response with HttpResult.Redirect().

How can I call a method when my web API is initialized?

I am building a web API that will serve as a connector between a 3rd-party application and mine.
This application will be running on a server and will be receiving POST requests from the 3rd-party application and sending POST requests of its own as a response.
Before it starts sending these requests, my web API needs to make a POST to the 3rd-party service, so it can be registered and received an authorization token, that it will be used on the requests it sends back, kinda similar to an OAuth token, from what I understand.
Since my code is all inside an HttpPost method, it only gets activated when it receives a call, and that part work as expected. When the service is authenticated and is receiving requests, is fine. The problem is when my service or the 3rd-party is restarted or something, the current token is made invalid or lost and a new one needs to be requested again.
What I wish to do is make that the call to register my service and receive the token is sent when the service starts, automatically.
Currently I am doing a manual call to trigger when my service needs to be registered, but that make it necessary for me to be at my computer to do so, and the connection is not make until I call that request.
Here is a sample of my code:
public class Controller : ApiController
{
static string SessionToken = "";
[HttpPost]
[Route("connector/webhook")]
public async Task<HttpStatusCode> Webhook(UpdateContentRequestBody body)
{
var NO_ERROR = 0;
try
{
if (string.IsNullOrEmpty(SessionToken))
{
// This registers my service.
var registerConector = ConectorOSCCApi.RegisterConector();
if (respostaRegistrarConector.ErrorCode != NO_ERROR)
{
throw new Exception();
}
SessionToken = registerConector.SessionToken;
}
ConectorApi.KeepAliveRequest(SessionToken);
RepeatKeepAlive();
ProccessDataAndSendResponseRequest(body);
return HttpStatusCode.OK;
}
catch (Exception e)
{
SessionToken = "";
return HttpStatusCode.InternalServerError;
}
I want the method to register the service to run without the need of a call to "connector/webhook", but the rest of the processing and response to only happens when such a call is received. How can I do that?
EDIT:
My code is inside a ASP.NET Web Application.
I am using .NET Framework 4.5 and hosting my web application on IIS.
This should do it for you :)
public class Controller : ApiController
{
static string _sessionToken = "";
static string SessionToken
{
get
{
if (string.IsNullOrEmpty(_sessionToken))
{
InitToken();
}
return _sessionToken
}
}
void InitToken()
{
if (string.IsNullOrEmpty(_sessionToken))
{
// This registers my service.
var registerConector = ConectorOSCCApi.RegisterConector();
if (respostaRegistrarConector.ErrorCode != NO_ERROR)
{
throw new Exception();
}
_sessionToken = registerConector.SessionToken;
}
}
public Controller() : base()
{
InitToken();
// anything else
}
[HttpPost]
[Route("connector/webhook")]
public async Task<HttpStatusCode> Webhook(UpdateContentRequestBody body)
{
var NO_ERROR = 0;
try
{
ConectorApi.KeepAliveRequest(SessionToken);
RepeatKeepAlive();
ProccessDataAndSendResponseRequest(body);
return HttpStatusCode.OK;
}
catch (Exception e)
{
SessionToken = "";
return HttpStatusCode.InternalServerError;
}
}
}
You don't need to wait for a request to your service to request a token.
Prerequisites : make sure you know what error code you receive from the third party API if your token is no longer correct.
When your API initializes, you will have a method available, ApplicationStart or something else in Startup.cs, depending on version, setup etc. Use that method to request the token from the third party API. Cache the token in the application level cache.
An example of caching can be found here: Caching Data in Web API
When your application receives a request, grab the token from the cache and issue the call to the third part API. If everything works, happy days. If it fails with token issue error code, then re-issue the token request and try again this time with the fresh token. Replace the cached token with the new one.
So basically, keep using a token until it fails, then automatically request a new one and update it. This way you don't need to be there to request the token manually.
You could wrap up this token logic into a service class so you don't have a lot to do in the endpoints.

Creating a proxy to another web api with Asp.net core

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();
}
}
}

Adding an Authorization header to a Service Reference SOAP request

I've consumed a WSDL as and have succesfully called web-service methods.The request has an Authorization header that can only be added at the point the request is made:
public static NumberCaptureClient Connect()
{
var remoteAddress = new EndpointAddress("https://website.com:8443/webservice/WebServiceNumberCapture");
using (var NumberCaptureClient = new NumberCaptureClient(new BasicHttpBinding(BasicHttpSecurityMode.Transport), remoteAddress))
{
NumberCapture.ClientCredentials.UserName.UserName = "test";
NumberCapture.ClientCredentials.UserName.Password = "test";
try
{
using (OperationContextScope scope = new OperationContextScope(NumberCaptureClient.InnerChannel))
{
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " +
Convert.ToBase64String(Encoding.ASCII.GetBytes(NumberCaptureClient.ClientCredentials.UserName.UserName + ":" + NumberCaptureClient.ClientCredentials.UserName.Password));
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
}
}
catch (Exception error)
{
MessageBox.Show("Error");
return null;
}
return NumberCaptureClient;
}
}
As you can see I'm in need of returning an instance of the proxy client (the client has hundereds of methods that all need the header) but need it so the headers are always sent, with the 'using' clause this isn't possible as the scope is lost outside of it.
Is there a way to permanantly add the headers so they are sent with every request to the webservice?
This is a WCF proxy, right? Generally speaking, you should remove the using from your Connect method. If the method is used to get a prepared service proxy, then it makes no sense to dispose it as part of the method that creates it.
Instead, the method/code that uses the Connect method should be responsible of using it:
using(var proxy = theClass.Connect())
{
// call service using proxy here
// process response here, if you may need to call the service again
// as part of processing
}
// process response here if you don't need to call the service again
There is a catch however, since for WCF proxies, the Dispose method internally calls the Close method, which in turn can throw exceptions. For this reason, Microsoft has a recommendation for how to handle cleaning up of WCF proxies. See here.

WCF proxy class creating null values

I have some problem on a WCF proxy class (not sure if it's the proxy or the service class), here is the context:
I have a WCF service that I consume on a web application, this service calls another service and then process the response to take it back to the web app. Here is the construction of that method
public CreateProjectResponse CreateNewProject(List<CreateProjectRequestProject> projects)
{
ServiceHelper helper = new ServiceHelper();
CreateProjectResponse response = helper.CreateNewProject(projects);
return response;
}
Everything is just fine up to the response object assignation. I have my correct list of "CreateProjectResponseProject" objects. The problem is that after the return statement I see that the service class is creating a NEW set of "CreateProjectResponseProject" objects as if it's calling the constructor again and assigning the default values (null in this case).
Does anyone have an idea what can be happening? I have been researching and don't seem to find any related solution. BTW... this process was working before, nothing have changed on the solution. Hope someone can help. Thanks!
EDIT: Here is the code for the helper class:
public class ServiceHelper
{
public CreateProjectResponse CreateNewProject(List<CreateProjectRequestProject> projects)
{
CreateProjectRequest request = new CreateProjectRequest();
CreateProjectResponse response = new CreateProjectResponse();
ProjectCreator create = new ProjectCreator();
WebServiceConfig configs = new WebServiceConfig();
request.Projects = projects;
configs.Password = "XXXXXXX";
configs.Username = "USER";
configs.RemoteAddress = "https://server/listener/connector";
configs.EndpoingConfig = "CreateProjectEndpoint";
try
{
response = create.CreateProject(configs, request);
}
catch (Exception ex)
{
string messageError = "unable to create project:" + ex.Message.ToString();
}
return response;
}
}
I was using the WCF service as an intermediate to communicate to another service, I removed the intermediate and called my helper class directly from the web application (with the proper endpoint config) and everything works fine now.

Categories

Resources