How to get a CompletableFuture from Jetty's HttpClient? - jetty

Is it possible to use issue an asynchronous HTTP request using Jetty and get back a CompletableFuture?
I read the docs but could not find any examples of doing so. I found internal usage of CompletableFuture but I couldn't figure out how to access it using the public API.
UPDATE: I need the CompletableFuture to return the response body as well (not just the response code and headers).

I have been using this with jetty client 9.4.x
var completable = new CompletableFuture<ContentResponse>();
client
.newRequest(uri)
.send(new CompletableFutureResponseListener(completable));
where
public class CompletableFutureResponseListener extends BufferingResponseListener {
private final CompletableFuture<ContentResponse> completable;
public CompletableFutureResponseListener(
CompletableFuture<ContentResponse> completable) {
this.completable = completable;
}
#Override
public void onComplete(Result result) {
if (result.isFailed()) {
completable.completeExceptionally(result.getFailure());
} else {
var response =
new HttpContentResponse(
result.getResponse(),
getContent(),
getMediaType(),
getEncoding());
completable.complete(response);
}
}
}

It's trivial to convert a CompleteListener into a CompletableFuture in this way:
CompletableFuture<Result> completable = new Promise.Completable<>();
httpClient.newRequest(...).send(result -> {
if (result.isFailed()) {
completable.completeExceptionally(result.getFailure());
} else {
completable.complete(result);
}
});
However, you are right that this may be done by HttpClient itself. Track this issue.

Related

Ktor+Koin: Why unit test still keep connect to mongodb even I mocked the service that injected with `CoroutineDatabase` instance

Note: I'm a Kotlin beginner
Developing stack
Ktor
Koin
KMongo: MongoDB
I defined my application to be like this
Route -> Service -> Repository
Route is define all HTTP request endpoints.
Service is business logic of application.
Repository is data access layer for query/persist data to MongoDB.
What I did to test a route, if the simple route like.
fun Route.healthCheck() {
get("/health") {
call.respond(HealthCheckResponse(message = "OK"))
}
}
#Test
fun testHealth() = testApplication {
application {
configureRouting()
}
client.get("/health").apply {
expect {
that(status).isEqualTo(HttpStatusCode.OK)
that(contentType()?.contentType).isNotNull().and {
contains(ContentType.Application.Json.contentType)
}
that(Json.decodeFromString(HealthCheckResponse.serializer(), bodyAsText()).message)
.isEqualTo("OK")
}
}
}
Test above will run good.
But when the case of the route that has DI, injecting CoroutineDatabase object into Repository then that repository inject it to Service and the service inject into Route.
In unit test code, I defined like below.
// Route define
fun Application.configureRouting() {
routing {
user()
}
}
fun Route.user() {
val userService: UserService by inject()
...
}
class UserServiceImpl(
private val userRepository: UserRepository // <- Repository is injected with MongoDB `CoroutineDatabase` object.
) : AccountService {
...
}
===============
// Unit Test
class UserEndpointTest: KoinTest {
#get:Rule
val koinTestRule = KoinTestRule.create {
modules(module{ single { accountService } })
}
#Test
fun testUserEndpoint() = testApplication {
application {
configureRouting() // -> collecting all extended function of `Route`
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
val testerEmail = "i_am_testing#the-email.dev"
val requestJson = """
{
"email": "$testerEmail",
"password": "aBCdEf9"
}
""".trimIndent()
val testBody = jsonMapper.decodeFromString(CreateAccountRequest.serializer(), requestJson)
coEvery { mockAccountService.submitUser(any()) } returns User(id = newId(), email = testerEmail, password = "")
client.post("$accountEndpoint/user") {
contentType(ContentType.Application.Json)
setBody(testBody)
}.apply {
expect {
that(status).isEqualTo(HttpStatusCode.Created)
that(contentType()?.contentType).isNotNull().and {
contains(ContentType.Application.Json.contentType)
}
that((body() as AccountResponse).id).isNotBlank()
that((body() as AccountResponse).email).isNotBlank().and {
isEqualTo(testerEmail)
}
}
}
}}
I expected I did mocking service and it should be inject into Route then it won't chaining call to repository and CoroutineDatabase object.
But when I run it keep connecting to database, I noticed with log below.
2022-09-07 02:51:30.093 [cluster-ClusterId{value='631788a0b273ac122eaa8350', description='null'}-localhost:27017] DEBUG org.mongodb.driver.cluster - Updating cluster description to {type=UNKNOWN, servers=[{address=localhost:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
And I tried to find out a tutorial to find how to write Unit Test with scenario like these, but I found most of answers on searched results are before Ktor version up. (Noticed from their codes still using withTestApplication {} which is deprecated already in version 2.1.0
Who can explain the step of Koin DI work and how to interrupt it with mock.
Thanks.

Unit testing Armeria's decorator using context.log().whenComplete()

I have a subclass of SimpleDecoratingHttpService that contains something like this:
override fun serve(ctx: ServiceRequestContext, req: HttpRequest): HttpResponse {
ctx.log().whenComplete().thenAccept {
if (it.responseCause() == ...) {
// do stuff
}
}
return unwrap().serve(ctx, req)
}
I want to test the logic inside the whenComplete() callback. However, when writing tests like this:
myDecorator.serve(context, request).aggregate().join()
the log() future never completes. What do I need to do to ensure that the log() future eventually completes?
Emulating RequestLog completion
A RequestLog is completed by Armeria's networking layer, so just consuming an HttpRequest or HttpResponse will not complete a RequestLog. To complete it, you need to call the methods in RequestLogBuilder:
var myDecorator = new MySimpleDecoratingHttpService(...);
var ctx = ServiceRequestContext.of(
HttpRequest.of(HttpMethod.GET, "/hello"));
var req = ctx.request();
var res = myDecorator.serve(ctx, ctx.req).aggregate().join();
// Fill the log.
ctx.logBuilder().endRequest();
assert ctx.log().isRequestComplete();
ctx.logBuilder().responseHeaders(ResponseHeaders.of(200));
ctx.logBuilder().endResponse();
assert ctx.log().isComplete();
Armeria team uses the same technique for testing BraveService, so you might want to check it out as well at BraveServiceTest.java:161.
Testing with a real server
If your setup is too complex to use a mock, as an alternative approach, you can launch a real Armeria server so that Armeria fills the log for you. You can easily launch a server using ServerRule (JUnit 4) or ServerExtension (JUnit 5):
class MyJUnit5Test {
static final var serviceContexts =
new LinkedBlockingQueue<ServiceRequestContext>();
#RegisterExtension
static final var server = new ServerExtension() {
#Override
protected void configure(ServerBuilder sb) throws Exception {
sb.service("/hello", (ctx, req) -> HttpResponse.of(200));
sb.decorator(delegate -> new MySimpleDecoratingHttpService(delegate, ...));
// Record the ServiceRequestContext of each request.
sb.decorator((delegate, ctx, req) -> {
serviceContexts.add(ctx);
return delegate.serve(ctx, req);
});
}
};
#BeforeEach
void clearServiceContexts() {
serviceContexts.clear();
}
#Test
void test() {
// Send a real request.
var client = WebClient.of(server.httpUri());
var res = client.get("/hello").aggregate().join();
// Get the ServiceRequestContext and its log.
var ctx = serviceContexts.take();
var log = sctx.log().whenComplete().join();
// .. check `log` here ..
assertEquals(200, log.responseHeaders().status().code());
}
}

Retrofit 2.1 Internal Server Error with Post

I am using Retrofit 2.1 and when i am posting an object to my server, it gives me Internal server error with status code = 500, but i try to to post from my backend, it works like a charm, I am sure this is not server's problem.
Undoubtedly, i should use retrofit as a singleton:
//return api if not null
HereApi getApi(){
if (api == null) {
api = getRetrofit().create(HereApi.class);
}
return api;
}
//returns restadapter if not null
Retrofit getRetrofit(){
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl("my endpoint")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
and this method that i post Here object:
void createHere(Here here){
List<Here> list = new ArrayList<>();
list.add(here);
Call<List<Here>> call = getApi().createHere(list);
call.enqueue(new Callback<List<Here>>() {
#Override
public void onResponse(Call<List<Here>> call, Response<List<Here>> response) {
Log.i(TAG, "onResponse: "+response.message());
}
#Override
public void onFailure(Call<List<Here>> call, Throwable t) {
}
});
}
I tried to post a list with single object inside and to post one object alone, but still status code is 500 ;*(
This is my api service interface:
public interface HereApi{
#GET("/lessons/")
Call<List<Lesson>> getLesson(#QueryMap Map<String,String> map);
#Headers({
"Content-Type: application/json",
"Vary: Accept"
})
#POST("/heres/")
Call<List<Here>> createHere(#Body List<Here> list);
#GET("/heres/")
Call<List<Here>> getHeres(#QueryMap Map<String,String> map);
}
I have written backend in Django + Django-rest-framework:
When I try to post from this, it just works:
I need your help guys, i have only one day to complete this project!!!
Hi I think there is a datetime conversation issue.
Use Jackson formating attonation in order to properly serialize datetime field.

Angular2 ASP.NET Core AntiForgeryToken

I have an Angular2 app. It is running within ASP.NET 5 (Core).
It makes Http calls to the controller which is working fine.
But now I need to establish Cross Site Scripting projection.
How do I generate a new token on each Http request and then subsequently perform the AntiForgeryToken check in Angular2 apps?
Note: My data forms in Angular are not produced from an MVC view but entirely written in Angular2 and call web services only.
All the examples I have seen are out dated and do not work / do not work fully.
How do I integrate AntiForgeryToken checks in Angular2 against ASP.NET 5 where forms are pure Angular?
Thanks.
A custom action filter is not necessary. It can all be wired up in Startup.cs.
using Microsoft.AspNetCore.Antiforgery;
(...)
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
(...)
}
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
app.Use(next => context =>
{
if (context.Request.Path == "/")
{
//send the request token as a JavaScript-readable cookie, and Angular will use it by default
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions { HttpOnly = false });
}
return next(context);
});
(...)
}
Then all you need in your controllers is the [ValidateAntiForgeryToken] decorator wherever you want to enforce that a token is provided.
For reference, I found this solution here - AspNet AntiForgery Github Issue 29.
I am using a action filter to send the request tokens.
Simply apply it to the actions you want a new antiforgery token, e.g. Angular2 SPA, WebAPI action, etc.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class AngularAntiForgeryTokenAttribute : ActionFilterAttribute
{
private const string CookieName = "XSRF-TOKEN";
private readonly IAntiforgery antiforgery;
public AngularAntiForgeryTokenAttribute(IAntiforgery antiforgery)
{
this.antiforgery = antiforgery;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
base.OnResultExecuting(context);
if (!context.Cancel)
{
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
CookieName,
tokens.RequestToken,
new CookieOptions { HttpOnly = false });
}
}
}
/* HomeController */
[ServiceFilter(typeof(AngularAntiForgeryTokenAttribute), IsReusable = true)]
public IActionResult Index()
{
return View();
}
/* AccountController */
[HttpPost()]
[AllowAnonymous]
[ValidateAntiForgeryToken]
// Send new antiforgery token
[ServiceFilter(typeof(AngularAntiForgeryTokenAttribute), IsReusable = true)]
public async Task<IActionResult> Register([FromBody] RegisterViewModel model)
{
//...
return Json(new { });
}
Register the attribute in Startup, and configure Antiforgery service to read the request token form "X-XSRF-TOKEN" header.
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddScoped<AngularAntiForgeryTokenAttribute>();
services.AddAntiforgery(options =>
{
options.HeaderName = "X-XSRF-TOKEN";
});
}
}
I think you need to make custom AntiForgeryValidationToken attribute that supports sending token via header instead of form values. Then add token to header of every request from your Angular2 app to your api. Example here How do you set global custom headers in Angular2?
To validate the token from a header you can use something like this:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class ValidateHeaderAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException(nameof(filterContext));
}
var httpContext = filterContext.HttpContext;
if (httpContext.Request.Headers["__RequestVerificationToken"] == null)
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
httpContext.Response.StatusDescription = "RequestVerificationToken missing.";
filterContext.Result = new JsonResult
{
Data = new { ErrorMessage = httpContext.Response.StatusDescription },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
return;
}
var cookie = httpContext.Request.Cookies[System.Web.Helpers.AntiForgeryConfig.CookieName];
System.Web.Helpers.AntiForgery.Validate(cookie != null ? cookie.Value : null, httpContext.Request.Headers["__RequestVerificationToken"]);
}
}
Then you just add [ValidateHeaderAntiForgeryToken] on the methods in your controller. Note though, this is from a MVC 5, ASP.NET 4.5.2 project, so you may have to alter it slightly to adjust to .NET Core. Also I modified this to return a JSON result if the token is missing, you can remove that part if you don't handle the error response and output it to the user.
Credits for the core part of this attribute goes to: https://nozzlegear.com/blog/send-and-validate-an-asp-net-antiforgerytoken-as-a-request-header
The hard part is how to generate the AntiForgeryToken without using #Html.AntiForgeryToken() in pure Angular 2 application (without access to .cshtml files). I'm looking for an answer to that as well.

WP8 SDK import Service Reference with task-based operations not possible

So far it seems that importing a service reference in VS2012 with "generate task-based operations" is not working. It os greyed out.
A test with a new project for WPF is working fine - I could select either task-based or async operations.
Is there a simple way on wrapping the async call in a task?
Is there a simple way on wrapping the async call in a task?
Example for WebClient.DownloadStringCompleted
public static class WebClientAsyncExtensions
{
public static Task<string> DownloadStringTask(this WebClient client, Uri address)
{
var tcs = new TaskCompletionSource<string>();
DownloadStringCompletedEventHandler handler = null;
handler = (sender, e) =>
{
client.DownloadStringCompleted -= handler;
if (e.Error != null)
{
tcs.SetException(e.Error);
}
else
{
tcs.SetResult(e.Result);
}
};
client.DownloadStringCompleted += handler;
client.DownloadStringAsync(address);
return tcs.Task;
}
}
Usage:
async void DownloadExample()
{
WebClient client = new WebClient();
await client.DownloadStringTask(new Uri("http://http://stackoverflow.com/questions/13266079/"));
}