How do I close a resource after responding using JAX-RS's CompletionStage async processing? - jax-rs

JAX-RS AsyncResponse's CompletionCallback and ConnectionCallback allow for the cleanup of resources after a response is completed or dropped.
#GET
public void get(#Suspend AsyncResponse response) {
executor.submit(() -> {
CloseableHttpResponse httpResponse = getHttpResponse();
response.register(closeOnCompleteOrDisconnect(httpResponse));
response.resume(Response.ok().entity(httpResponse.getEntity().getContent()).build());
// We couldn't close the httpResponse now because the entity content hasn't been read.
// it can't be closed till the message body writer finishes streaming the response.
// hence the closeOnCompleteOrDisconnect callback.
});
}
I'd like to convert this code to CompletableFuture, but don't know how to close the httpResponse after the message body has been written.
#GET
public CompletableFuture<Response> getAsync() {
return CompletableFuture.supplyAsync(() -> {
CloseableHttpResponse httpResponse = getHttpResponse();
Response.ok().entity(httpResponse.getEntity().getContent()).build();
// still can't close the httpResponse, and now we've no AsyncResponse to register a callback on.
}, executor);
}
https://download.oracle.com/otn-pub/jcp/jaxrs-2_1-final-eval-spec/jaxrs-2_1-final-spec.pdf?AuthParam=1605738947_546c3c96f63881e7441d4b95f55d00e7

Related

Cannot Interrupt HttpClient.GetStreamAsync by means of CancellationToken in ASP.NET Core

I'm trying to send an http request to an AXIS Camera in order to receive a stream.
Everything works fine except that I can't get to use CancellationToken to cancel the request when it is no more needed. I've the following architecture:
Blazor client:
// LiveCamera.razor
<img src="CameraSystem/getStream" onerror="[...]" alt="">
ASP.NET Core Server:
// CameraSystemController.cs
[ApiController]
[Route("[controller]")]
public class CameraSystemController : Controller
{
[HttpGet("getStream")]
public async Task<IActionResult> GetStream()
{
Stream stream = await Device_CameraStandard.GetStream();
if (stream != null) {
Response.Headers.Add("Cache-Control", "no-cache");
FileStreamResult result = new FileStreamResult(stream, _contentTypeStreaming) {
EnableRangeProcessing = true
};
return result;
} else {
return new StatusCodeResult((int)HttpStatusCode.ServiceUnavailable);
}
}
}
Class accessing the camera:
// Device_CameraStandard.cs
internal class Device_CameraStandard
{
private HttpClient _httpClient;
private static CancellationTokenSource _tokenSource;
private System.Timers.Timer _keepAliveTimer;
internal Device_CameraStandard() {
_keepAliveTimer = new System.Timers.Timer();
_keepAliveTimer.Interval = 3000;
_keepAliveTimer.Elapsed += KeepAliveTimeout;
_tokenSource = new CancellationTokenSource();
[...]
}
internal async Task<Stream> GetStream()
{
return await _httpClient.GetStreamAsync("http://[...]/axis-cgi/mjpg/video.cgi?&camera=1", _tokenSource.Token);
}
// Invoked periodically by client from LiveCamera.razor.cs, not included here
internal void KeepAlive()
{
LLogger.Debug("KeepAlive!");
_keepAliveTimer.Stop();
_keepAliveTimer.Start();
}
private void KeepAliveTimeout(object sender, ElapsedEventArgs e)
{
LLogger.Debug("Timeout!");
_keepAliveTimer.Stop();
_tokenSource.Cancel();
_tokenSource.Dispose();
_tokenSource = new CancellationTokenSource();
}
}
However, even if all clients leave LiveCamera.razor page and the _keepAliveTimer elapses and the CancellationTokenSource is canceled, the request is not canceled. I can see it from the fact that bandwidth usage does not decreases (the "receiving" bandwitdh, indicating that Server is still receiving data from camera), it only decreases if I close the browser tab.
Could you please help me to understand what am I doing wrong? Thanks
EDIT: In the end, even after following the suggestion of observing the token in all code parts where the returned stream was used, included the controller, I ended up discovering that the tag
// LiveCamera.razor
<img src="CameraSystem/getStream" onerror="[...]" alt="">
was causing the client to never stop sending requests. Thus I had to use a workaround to force client to stop sending requests before leaving LiveCamera.razor page.

Reading RequestBody distrupts flow in ASP.NET Core 2.2 gateway

I have a middleware to track performance of my custom developed gateway in ASP.NET Core 2.2 API. I have used the this post from StackOverflow.
Basically the main part is as follows :
public class ResponseRewindMiddleware {
private readonly RequestDelegate next;
public ResponseRewindMiddleware(RequestDelegate next) {
this.next = next;
}
public async Task Invoke(HttpContext context) {
Stream originalBody = context.Response.Body;
/* MY CODE COMES HERE */
try {
using (var memStream = new MemoryStream()) {
context.Response.Body = memStream;
await next(context);
memStream.Position = 0;
string responseBody = new StreamReader(memStream).ReadToEnd();
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
}
} finally {
context.Response.Body = originalBody;
}
}
This code runs OK. But I want to log the input (a JSON body) to the gateway and I add the following lines :
using (System.IO.StreamReader rd = new System.IO.StreamReader(context.Request.Body))
{
bodyStr = rd.ReadToEnd();
}
This reads the input body from Request but the flow is broken and the rest of the process does not flow resulting in a "HTTP 500 Internal Server Error". I assume reading the Request body via a Stream breaks something.
How can I read the Request body without breaking the flow?
The idea is to call EnableBuffering to enable multiple read, and then to not dispose the request body after you have done reading it. The following works for me.
// Enable the request body to be read in the future
context.Request.EnableBuffering();
// Read the request body, but do not dispose it
var stream = new StreamReader(context.Request.Body);
string requestBody = await stream.ReadToEndAsync();
// Reset to the origin so the next read would start from the beginning
context.Request.Body.Seek(0, SeekOrigin.Begin);

Model binding stopped working when added custom middleware for request logging

I am using following class to log all request and responses to my API. The code is taken from link https://exceptionnotfound.net/using-middleware-to-log-requests-and-responses-in-asp-net-core/. The problem is when i register this middleware my model binding stops working. The request is always null. I think the problem is into the method "FormatRequest", if i remove the call to that method it starts working, but cannot figure out why it is disrupting model binding process.
public class RequestResponseLoggingMiddleware
{
private readonly RequestDelegate _next;
public RequestResponseLoggingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
//First, get the incoming request
var request = await FormatRequest(context.Request);
//Copy a pointer to the original response body stream
var originalBodyStream = context.Response.Body;
//Create a new memory stream...
using (var responseBody = new MemoryStream())
{
//...and use that for the temporary response body
context.Response.Body = responseBody;
//Continue down the Middleware pipeline, eventually returning to this class
await _next(context);
//Format the response from the server
var response = await FormatResponse(context.Response);
//TODO: Save log to chosen datastore
//Copy the contents of the new memory stream (which contains the response) to the original stream, which is then returned to the client.
await responseBody.CopyToAsync(originalBodyStream);
}
}
private async Task<string> FormatRequest(HttpRequest request)
{
var body = request.Body;
//This line allows us to set the reader for the request back at the beginning of its stream.
request.EnableRewind();
//We now need to read the request stream. First, we create a new byte[] with the same length as the request stream...
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
//...Then we copy the entire request stream into the new buffer.
await request.Body.ReadAsync(buffer, 0, buffer.Length);
//We convert the byte[] into a string using UTF8 encoding...
var bodyAsText = Encoding.UTF8.GetString(buffer);
//..and finally, assign the read body back to the request body, which is allowed because of EnableRewind()
request.Body = body;
return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
}
private async Task<string> FormatResponse(HttpResponse response)
{
//We need to read the response stream from the beginning...
response.Body.Seek(0, SeekOrigin.Begin);
//...and copy it into a string
string text = await new StreamReader(response.Body).ReadToEndAsync();
//We need to reset the reader for the response so that the client can read it.
response.Body.Seek(0, SeekOrigin.Begin);
//Return the string for the response, including the status code (e.g. 200, 404, 401, etc.)
return $"{response.StatusCode}: {text}";
}
}
This is how i am registering it,
public class Startup
{
//...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//Add our new middleware to the pipeline
app.UseMiddleware<RequestResponseLoggingMiddleware>();
app.UseMvc();
}
}
For this issue, you could try request.Body.Seek(0, SeekOrigin.Begin); to reset body instead of request.Body = body;
private async Task<string> FormatRequest(HttpRequest request)
{
var body = request.Body;
//This line allows us to set the reader for the request back at the beginning of its stream.
request.EnableRewind();
//We now need to read the request stream. First, we create a new byte[] with the same length as the request stream...
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
//...Then we copy the entire request stream into the new buffer.
await request.Body.ReadAsync(buffer, 0, buffer.Length);
//We convert the byte[] into a string using UTF8 encoding...
var bodyAsText = Encoding.UTF8.GetString(buffer);
//..and finally, assign the read body back to the request body, which is allowed because of EnableRewind()
//request.Body = body;
request.Body.Seek(0, SeekOrigin.Begin);
return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
}

Set timeout for HTTPClient get() request

This method submits a simple HTTP request and calls a success or error callback just fine:
void _getSimpleReply( String command, callback, errorCallback ) async {
try {
HttpClientRequest request = await _myClient.get( _serverIPAddress, _serverPort, '/' );
HttpClientResponse response = await request.close();
response.transform( utf8.decoder ).listen( (onData) { callback( onData ); } );
} on SocketException catch( e ) {
errorCallback( e.toString() );
}
}
If the server isn't running, the Android-app more or less instantly calls the errorCallback.
On iOS, the errorCallback takes a very long period of time - more than 20 seconds - until any callback gets called.
May I set for HttpClient() a maximum number of seconds to wait for the server side to return a reply - if any?
There are two different ways to configure this behavior in Dart
Set a per request timeout
You can set a timeout on any Future using the Future.timeout method. This will short-circuit after the given duration has elapsed by throwing a TimeoutException.
try {
final request = await client.get(...);
final response = await request.close()
.timeout(const Duration(seconds: 2));
// rest of the code
...
} on TimeoutException catch (_) {
// A timeout occurred.
} on SocketException catch (_) {
// Other exception
}
Set a timeout on HttpClient
You can also set a timeout on the HttpClient itself using HttpClient.connectionTimeout. This will apply to all requests made by the same client, after the timeout was set. When a request exceeds this timeout, a SocketException is thrown.
final client = new HttpClient();
client.connectionTimeout = const Duration(seconds: 5);
You can use timeout
http.get(Uri.parse('url')).timeout(
const Duration(seconds: 1),
onTimeout: () {
// Time has run out, do what you wanted to do.
return http.Response('Error', 408); // Request Timeout response status code
},
);
The HttpClient.connectionTimeout didn't work for me. However, I knew that the Dio packet allows request cancellation. Then, I dig into the packet to find out how they achieve it and I adapted it to me. What I did was to create two futures:
A Future.delayed where I set the duration of the timeout.
The HTTP request.
Then, I passed the two futures to a Future.any which returns the result of the first future to complete and the results of all the other futures are discarded. Therefore, if the timeout future completes first, your connection times out and no response will arrive. You can check it out in the following code:
Future<Response> get(
String url, {
Duration timeout = Duration(seconds: 30),
}) async {
final request = Request('GET', Uri.parse(url))..followRedirects = false;
headers.forEach((key, value) {
request.headers[key] = value;
});
final Completer _completer = Completer();
/// Fake timeout by forcing the request future to complete if the duration
/// ends before the response arrives.
Future.delayed(timeout, () => _completer.complete());
final response = await Response.fromStream(await listenCancelForAsyncTask(
_completer,
Future(() {
return _getClient().send(request);
}),
));
}
Future<T> listenCancelForAsyncTask<T>(
Completer completer,
Future<T> future,
) {
/// Returns the first future of the futures list to complete. Therefore,
/// if the first future is the timeout, the http response will not arrive
/// and it is possible to handle the timeout.
return Future.any([
if (completer != null) completeFuture(completer),
future,
]);
}
Future<T> completeFuture<T>(Completer completer) async {
await completer.future;
throw TimeoutException('TimeoutError');
}
This is an example of how to extend the http.BaseClient class to support timeout and ignore the exception of the S.O. if the client's timeout is reached first.
you just need to override the "send" method...
the timeout should be passed as a parameter to the class constructor.
import 'dart:async';
import 'package:http/http.dart' as http;
// as dart does not support tuples i create an Either class
class _Either<L, R> {
final L? left;
final R? right;
_Either(this.left, this.right);
_Either.Left(L this.left) : right = null;
_Either.Right(R this.right) : left = null;
}
class TimeoutClient extends http.BaseClient {
final http.Client _httpClient;
final Duration timeout;
TimeoutClient(
{http.Client? httpClient, this.timeout = const Duration(seconds: 30)})
: _httpClient = httpClient ?? http.Client();
Future<http.StreamedResponse> send(http.BaseRequest request) async {
// wait for result between two Futures (the one that is reached first) in silent mode (no throw exception)
_Either<http.StreamedResponse, Exception> result = await Future.any([
Future.delayed(
timeout,
() => _Either.Right(
TimeoutException(
'Client connection timeout after ${timeout.inMilliseconds} ms.'),
)),
Future(() async {
try {
return _Either.Left(await _httpClient.send(request));
} on Exception catch (e) {
return _Either.Right(e);
}
})
]);
// this code is reached only for first Future response,
// the second Future is ignorated and does not reach this point
if (result.right != null) {
throw result.right!;
}
return result.left!;
}
}
Their is onError option which works fine if their is any exception like no internet.It has to return response(my case in below code) or null.
In response their are 2 options Body and Status code.
var response = await http.post(url, body: body, headers: _headers).onError(
(error, stackTrace) => http.Response(
jsonEncode({
'message':no internet please connect to internet first
}),
408));
You can also choose to override the settings for a HttpClient:
class DevHttpOverrides extends HttpOverrides {
#override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..connectionTimeout = Duration(seconds: 2);
}
}

OKHttp Authenticator custom http code other than 401 and 407

I have oauth token implemented on server side but upon Invalid token or Token expirey i am getting 200 http status code but in response body i have
{"code":"4XX", "data":{"some":"object"}
When i try to read string in interceptor i get okhttp dispatcher java.lang.illegalstateexception closed because response.body().string() must be called only once.
Also i read from here Refreshing OAuth token using Retrofit without modifying all calls that we can use OkHttp Authenticator class but it works only with 401/407 i havent triedn as i will not get this. Is there any way we can customize Authenticator and proceed our logic inside it.
Thank you
If it possible, try to talk with your server side about response codes. Communication is also a very important skill.
If it inpossible, you can modify response codes manually with reflection, it enables okHttp authentication logic.
public OkHttpClient getOkHttpClient() {
return new OkHttpClient.Builder()
.authenticator((route, response) -> {
System.out.println("it working");
return null;
})
.addNetworkInterceptor(new UnauthorizedCaseParserInterceptor())
.build();
}
public class UnauthorizedCaseParserInterceptor implements Interceptor {
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (isUnauthorizedResponse(response)) {
try {
Field codeField = response.getClass().getDeclaredField("code");
codeField.setAccessible(true);
codeField.set(response, HttpURLConnection.HTTP_UNAUTHORIZED);
} catch (Exception e) {
return response;
}
}
return response;
}
private boolean isUnauthorizedResponse(Response response) {
//parse response...
}
}
Please use this solution only as a last resort.