How to use a simple HTTP proxy with Reactor Netty client? - http

I'm trying to use a simple HTTP proxy to externally cache the responses. I tried the following snippet:
public static void main(String[] args) {
HttpClient httpClient = HttpClient.builder().options(httpOptions -> {
httpOptions.proxy(proxyOptions -> proxyOptions
.type(Proxy.HTTP)
.address(InetSocketAddress.createUnresolved("localhost", 7000))
);
}).build();
String url = "http://httpbin.org/get";
HttpClientResponse response = httpClient.get(url).block();
System.out.println(response == null ? "null" : response.status().code());
}
... but the result is that the client starts with a CONNECT command, creating a TCP tunnel.
When I work with curl, I do the following:
curl "http://httpbin.org/get" -x "localhost:7000"
... and it simply issues a regular HTTP request against the proxy.
My question: how do I use Raector-Netty to issue a regular (not a CONNECT based) request against a proxy?

ReactorClientHttpConnector connector = new ReactorClientHttpConnector(options ->
options.httpProxy(addressSpec -> {
return addressSpec.host("http://proxy_server").port(proxy_port); }));
webClient = WebClient.builder()
.clientConnector(connector)
.baseUrl(baseUrl)
.build();

Related

Set headers for grpc-web call

I'm currently facing an issue with grpc-web, and a loadbalancer.
Trying to call our grpc webservices from our gateway API, results in the following error:
Status(StatusCode="Unknown", Detail="Bad gRPC response. HTTP status code: 411")
It appears that the either of the following headers are required, content-length or Transfer-Encoding.
I have a method for setting metadata in my client.
private async Task<Metadata> SetMetadata()
{
//More stuff here
headers.Add("Transfer-Encoding", "chunked");
return headers;
}
Here is how i create my client:
private async Task<Services.Protobuf.ServiceClient> CreateClient()
{
var httpMessageHandler = new HttpClientHandler();
_grpcChannel ??= GrpcChannel.ForAddress(
await _serviceAddressProvider.GetServiceAddress<ServiceClient>() ??
throw new InvalidOperationException(),
new GrpcChannelOptions()
{
HttpHandler = new GrpcWebHandler(httpMessageHandler)
});
return new(_grpcChannel);
}
And here is how i use the two
var serviceClient = await CreateClient();
var request = new Request
{
//Request stuff
};
var getListReply = await serviceClient.GetListReplyAsync(request, await SetMetadata());
Now. The issue is that I cannot set either Transfer-Encoding or Content-Lenght headers. They simply get stripped somewhere.
If fiddler is running they get added (by fiddler i assume), and the request actually works. But if fiddler is not running, the headers are not there, and i get the above error. (I honestly don't understand the part with fiddler, i'm only reporting what i'm seeing).
Does anyone have any idea why this happens? and if it's even possible to add the headers i'm trying to add with grpc-web?
I don't know much about grpc-web but grpc-gateway does strip HTTP headers if they don't have a grpcmetadata prefix when it forwards the HTTP request to the grpc server
You can take a look at this issue thread https://github.com/grpc-ecosystem/grpc-gateway/issues/1244

Apache Http EntityUtils.consume() vs EntityUtils.toString()?

I have written a HTTP client, where I am reading the data response from a REST web service. My confusion arises after reading multiple blogs on EntityUtils.consume() and EntiryUtils.toString(). I wanted to know the following:
If EntityUtils.toString(..) ONLY is sufficient as it also closes the stream after reading char bytes. Or I should also do EntityUtils.consume(..) as a good practice.
If both toString() and consume() operation can be used. If yes, then what should be there order.
If I EntityUtils.toString() closes the stream; then why the next call in EntityUtils.consume(..) operations which is entity.isStreaming() still returns true?
Could anyone guide me here to use these operations in a standard way. I am using HTTP version 4+.
I have to use these configurations in multithreaded(web-app) environment.
Thanks
I looked at the recommended example from the apache httpclient commons website.
In the example, they used EntityUtils.toString(..) without needing to use EntityUtils.consume(..) before or after.
They mention that calling httpclient.close() ensures all resources are closed.
source: https://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientWithResponseHandler.java
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
HttpGet httpget = new HttpGet("http://httpbin.org/");
System.out.println("Executing request " + httpget.getRequestLine());
// Create a custom response handler
ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
#Override
public String handleResponse(
final HttpResponse response) throws ClientProtocolException, IOException {
int status = response.getStatusLine().getStatusCode();
if (status >= 200 && status < 300) {
HttpEntity entity = response.getEntity();
return entity != null ? EntityUtils.toString(entity) : null;
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
}
};
String responseBody = httpclient.execute(httpget, responseHandler);
System.out.println("----------------------------------------");
System.out.println(responseBody);
} finally {
httpclient.close();
}
This is what is quoted for the above example:
This example demonstrates how to process HTTP responses using a response handler. This is the recommended way of executing HTTP requests and processing HTTP responses. This approach enables the caller to concentrate on the process of digesting HTTP responses and to delegate the task of system resource deallocation to HttpClient. The use of an HTTP response handler guarantees that the underlying HTTP connection will be released back to the connection manager automatically in all cases.

how to update the incoming request in pact jvm requestFilter?

I have a spring boot API in java which is using pact-jvm for pact verification.
We have a new client who wants to use the same API using a new path, which the gateway will take care of, but this causes issue for pacts, I want to intercept the request and modify the path of the request for new pacts to point to old path.
I was trying to refer some material online and found this :
https://medium.com/dazn-tech/pact-contract-testing-dealing-with-authentication-on-the-provider-51fd46fdaa78
The below code prints the updated value of the request, but the pact still fails with 404 error as if it is still using new path
requestFilter = { req ->
println "incoming request : $req"
if ("$req".contains('/new-context') ) {
req = "$req".replace('/new-context', '/old-context')
println "updated request : $req"
}
}
The problem in the above code was I was treating req as string and doing manipulations, but it is an HttpRequest object and the below code solved the issue for me:
requestFilter = { req ->
def uriText = req.getURI()
println "incoming request uri : $uriText"
if ("$uriText".contains('/new-context') ) {
def uriTextNew = "$uriText".replace('/new-context', '/old-context')
println "updated request uri : $uriTextNew"
URI newURI = new URI(uriTextNew)
req.setURI(newURI)
}
}

Can you run a asp.net core 3.0 gRPC CLIENT in IIS? (possibly on Azure?)

I've read a lot of conflicting information about this and it seems people are not 100% clear on what is possible and what is not. I am certain that you cannot host a gRPC server app in IIS due to the HTTP/2 limitations. The documentation is pretty clear. However, I want to use IIS as a reverse proxy, with the internal side communicating using gRPC. So the client would be in IIS, not the server. I assumed that since the communication at this point (i.e. the back end) was not funneled through IIS, there would be no issue with this. However, I keep seeing mixed answers.
I have created a dumb webapp that is hosted in IIS Express and can successfully post to my service running on Kestrel with gRPC.
Client code sample below. The SubmitButton is just a form post on the razor page.
public async void OnPostSubmitButton()
{
// The port number(5001) must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
Server code is the boilerplate template for gRPC but looks like this:
namespace grpcGreeter
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
namespace grpcGreeter
{
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
}
This works. But, because I keep seeing mixed information saying it that it won't, I am not certain that once I go to deploy the client code (i.e. the reverse proxy), if I will run into problems. I would like to use a host like Azure...but don't know if it's possible or not.
Any clarity on the subject would be greatly appreciated.
As far as I know, we could use asp.net core mvc or razor page application as the client to call the grpc server.
But gRPC client requires the service to have a trusted certificate when you hosted the application on remote server IIS.
If you don't have the permission to install the certificate, you should uses HttpClientHandler.ServerCertificateCustomValidationCallback to allow calls without a trusted certificate.
Notice: this will make the call not security.
Additional configuration is required to call insecure gRPC services with the .NET Core client. The gRPC client must set the System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport switch to true and use http in the server address.
Code as below:
AppContext.SetSwitch(
"System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var httpClientHandler = new HttpClientHandler();
// Return `true` to allow certificates that are untrusted/invalid
httpClientHandler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
var httpClient = new HttpClient(httpClientHandler);
var channel = GrpcChannel.ForAddress("https://localhost:5001",
new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });

Best way to have one Web API forward request to other

I have a few web services running on different servers, and I want to have one web service running "in front" of the rest to decide which web service (server) the request should be forwarded to based on header values.
The idea is that a client will send a request, say:
http://api.mysite.com/cars
The API at mysite.com will inspect the request, extract information from the API key (which is supplied in the headers) and redirect to the appropriate server, e.g.
http://server4.mysite.com/api/cars
Is this going to work? I'm concerned about how I will return the response (w/data) from "server4" to the client. Will the response only be returned back to the first server or will the client achieve that response?
Just run into the same task and have to add some more lines in addition to Yazan Ati answer.
[HttpPost]
[HttpGet]
[Route("api/TestBot/{*remaining}")]
public Task<HttpResponseMessage> SendMessage()
{
const string host = "facebook.botframework.com";
string forwardUri = $"https://{host}/api/v1/bots/billycom{Request.RequestUri.Query}";
Request.Headers.Remove("Host");
Request.RequestUri = new Uri(forwardUri);
if (Request.Method == HttpMethod.Get)
{
Request.Content = null;
}
var client = new HttpClient();
return client.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead);
}
All you need to do is build a Web API DelegatingHandler like this:
public class ProxyHandler : DelegatingHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
UriBuilder forwardUri = new UriBuilder(request.RequestUri);
//strip off the proxy port and replace with an Http port
forwardUri.Port = 80;
//send it on to the requested URL
request.RequestUri = forwardUri.Uri;
HttpClient client = new HttpClient();
var response = await client.SendAsync(request,HttpCompletionOption.ResponseHeadersRead);
return response;
}
}

Resources