My backend API has public and authenticated methods.
I'm trying to encapsulate everything using Cascade and Pipelines, but the cascade seems to quit immediately after the first pipeline if the route isn't found.
In short, I want all requests to be logged using the logRequests middleware and the private requests to go through a custom middleware which validate credentials.
Here's the code:
final publicPipeline = Pipeline()
.addMiddleware(logRequests())
.addHandler(Router()
..head('/health', _health)
..post('/login', _login));
final privatePipeline = Pipeline()
.addMiddleware(_validateCredentials)
.addHandler(Router()
..head('/get_data', _getData)
..post('/logout', _logout));
final cascadeHandler = Cascade()
.add(publicPipeline)
.add(privatePipeline)
.handler;
await io.serve(cascadeHandler, '0.0.0.0', port);
Removing the Pipelines and using only Routers works as expected, but then I lose the flexibility of using Middlewares.
Related
I am using compute to do some work while keeping the UI running. The compute was working until I added another http call before it.
The working code is as follow
final ListRequest request =
ListRequest(baseUrl: env['SERVER_URL']!, path: '/Items');
_mainController.updateListItems(
await compute(_service.getItems, request));
I read some articles saying the function compute calls should be a top level function or a static function. However, the getItems is an instance function and there was no exception.
Recently I added a few lines and the code became
final Filter? filter = await _service.getFilter();
final ListRequest request =
ListRequest(baseUrl: env['SERVER_URL']!, path: '/Items');
request.filter = filter;
_mainController.updateListItems(
await compute(_service.getItems, request));
getFilter is a http call to retrieve some filter parameters from the backend.
Then I got the following error
Invalid argument(s): Illegal argument in isolate message: (object extends NativeWrapper - Library:'dart:io' Class: _SecureFilterImpl#13069316)
My dart and flutter versions are
Dart SDK version: 2.15.1 (stable)
Flutter 2.8.1
Thank you
=========================================================
Update
The Filter is
Filter {
String? itemLocationSuburb;
String? itemLocationPostcode;
}
Your _service service presumably contains a HttpClient. When you make a request through this client, it opens a connection to the HTTP server, and may maintain the connection after the request completes.
The HttpClient cannot be sent through a SendPort when it has open connections, but it is included in the scope of the getItems method.
To work around this issue, you can do one of the following:
Disable persistent connections with the HttpClientRequest.persistentConnection property
Make a new HttpClient to send through the compute function every time
Implement a long-lived background isolate to maintain its own HttpClient
Use the HttpClient in the main isolate, and only perform other work like parsing with compute (there's no significant benefit to using an isolate to make HTTP requests anyway)
Having moved my mobile app development to Flutter I am now in the process of experimenting with using Dart as my main server side language. The productivity benefits in using a single coding language in both the app and on the server are considerable. To that end I have set up a server with an Nginx front end which proxies all dynamic web requests to an Angel/Dart server.
Angel is a remarkably well written package and I had a working server written up in no time at all. However, in order to have a fully functional backend I need to be able to use both Redis and PostgreSQL from within my server side Dart code. I am using the resp_client package to access Redis. The issue I have run into is with the fact that RespCommand.get is asynchronous. With my newbie knowledge of both Dart and Angel I am unable to find a way to acquire a Redis key value via RespCommand.get in an Angel route handler and then somehow use that value in the response it returns.
My entire Dart backend server code is shown below
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:postgres/postgres.dart';
import 'package:resp_client/resp_client.dart';
import 'package:resp_client/resp_commands.dart';
class DartWeb
{
static Angel angel;
static AngelHttp http;
static RespCommands redis;
static PostgreSQLConnection db;
static init() async
{
angel = Angel();
http = AngelHttp(angel);
angel.get('/',rootRoute);
await prepareRedis();
await http.startServer('localhost',3000);
}
static prepareRedis() async
{
RespServerConnection rsc = await connectSocket('localhost');
RespClient client = RespClient(rsc);
redis = RespCommands(client);
}
static preparePostgres() async
{
db = new PostgreSQLConnection('serverurl',portNo,'database',username:'user',password:'password');
await db.open();
}
static void rootRoute(RequestContext req,ResponseContext res)
{
try
{
await redis.set('test','foobar',expire:Duration(seconds:10));
String testVal = await redis.get('test');
res.write('Done $testVal');
} catch(e) {res.write('++ $e ++');}
}
}
main() async {await DartWeb.init();}
If I start up this server and then access it through my web browser I end up with a 502 Bad Gateway message. Not surprising. dart2native main.dart -o mainCompiled returns the error await can only be used in async... message.
So I tried instead
try
{
res.write('Before');
redis.set('test','foobar',expire:Duration(seconds:10)).then((bool done)
{
res.write('DONE $done');
});
res.write('After');
} catch(e) {res.write('++ $e ++');}
which simply printed out BeforeAfter in my browser with the DONE bit never showing up although a quick test via redis-cli shows that the key test had in fact been created.
My knowledge of both Dart and Angel is still in its infancy so I guess I am doing something incorrectly here. Shorn of all the detail my questions are essentially these -
how do I call and get the result from async methods in an Angel route dispatcher?
given that I am editing my Dart code in VSCode on my local Windows machine which accesses the relevant dart files on my Ubuntu server I loose the benefits of error reporting provided by the VSCode Dart plugin. dart2native, as I have used here, helps out but it would be nicer if I could somehow get a running error report within VSCode as I do when building Flutter apps locally. How can I accomplish this - if at all possible?
It turns out that Dart/Angel does not impose excessively strict constraints on the signature of a route handler. So you can quite safely declare a route handler like this one
static Future<void> rootRoute(RequestContext req,ResponseContext res) async
{
try
{
res.write('!! Before ');
await redis.set('test','foobar',expire:Duration(seconds:10));
String test = await redis.get('test');
res.write('After $test !!');
} catch(e) {res.write('++ $e ++');}
}
With the route simply returning a Future we can now safely do anything we like there - including calling other asynchronous methods: in this instance to fetch a Redis key value.
Changing the Order of the Spring Security WebFilter
I have an API Gateway implemented using Spring Cloud Gateway that uses Spring Security. Spring Security for WebFlux is implemented as a WebFilter right at the beginning of the filter chain. So after successful authentication the request would be forwarded to Spring Cloud Gateway's RoutePredicateHandlerMapping, which would try to deduce the destination based on the URL pattern, and then it would go to a FilteringWebHandler to execute the other filters of Spring Cloud Gateway.
My problem is the following: I have implemented a customized authentication algorithm which uses query string and header variables as credentials for authentication according to the requirements of the project, an this is working without any problem. The problem occurred when we needed to add a small customization for the authentication algorithm that is path independent. When the request reaches the WebFilter of Spring Security, pattern matching is not yet done so I do not know which application does it point to, for example:
app1:
-Path: /app1/**
app2:
-Path: /app2/**
Which means that instead of having authentication -> route mapping -> filtering web handler I should do route mapping -> authentication -> filtering web handler. Not that these three components are not similar, one of them is a filter another is a mapper and the last one is web handler. Now I know how to customize them but the problem is that I do not know how to intercept the Netty server building process in order to change the order of these operations. I need to wait for the building process to end and alter the content of the server before it starts. How can I do that?
EDIT: here is the final solution:
So here is how I did it:
Goal: removing the WebFilter of Spring Security from the default HttpHandler, and inserting it between RoutePredicateRouteMapping and the FilteringWebHandler of Spring Cloud Gateway
Why: Because I need to know the Application ID while carrying on my customized authentication process. This Application ID is attached to the request by the RoutePredicateRouteMapping by matching the request's URL to a predefined list.
How did I do it:
1- Removing the WebFilter of Spring Security
I created an HttpHandler bean that invokes the default WebHttpHandlerBuilder and then customize the filters. As a bonus, I removed unneeded filters in order to increase the performance of my API Gateway
#Bean
public HttpHandler httpHandler() {
WebHttpHandlerBuilder webHttpHandlerBuilder = WebHttpHandlerBuilder.applicationContext(this.applicationContext);
MyAuthenticationHandlerAdapter myAuthenticationHandlerAdapter = this.applicationContext.getBean(MY_AUTHENTICATED_HANDLER_BEAN_NAME, MyAuthenticationHandlerAdapter.class);
webHttpHandlerBuilder
.filters(filters ->
myAuthenticationHandlerAdapter.setSecurityFilter(
Collections.singletonList(filters.stream().filter(f -> f instanceof WebFilterChainProxy).map(f -> (WebFilterChainProxy) f).findFirst().orElse(null))
)
);
return webHttpHandlerBuilder.filters(filters -> filters
.removeIf(f -> f instanceof WebFilterChainProxy || f instanceof WeightCalculatorWebFilter || f instanceof OrderedHiddenHttpMethodFilter))
.build();
}
2- Wrapping Spring Cloud Gateway's FilteringWebHandler with Spring Web's FilteringWebHandler with the added WebFilter
I created my own HandlerAdapter which would match against Spring Cloud Gateway's FilteringWebHandler and wrap it with Spring Web's FilteringWebHandler plus the security filter I extracted in the first step
#Bean
public MyAuthenticationHandlerAdapter myAuthenticationHandlerAdapter() {
return new MyAuthenticationHandlerAdapter();
}
public class MyAuthenticationHandlerAdapter implements HandlerAdapter {
#Setter
private List<WebFilter> securityFilter = new ArrayList<>();
#Override
public boolean supports(Object handler) {
return handler instanceof FilteringWebHandler;
}
#Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
org.springframework.web.server.handler.FilteringWebHandler filteringWebHandler = new org.springframework.web.server.handler.FilteringWebHandler((WebHandler) handler, securityFilter);
Mono<Void> mono = filteringWebHandler.handle(exchange);
return mono.then(Mono.empty());
}
}
This way I could achieve better performance with highly customized HttpHandler pipeline that I suppose to be future-proof
END EDIT
Spring Security for WebFlux is implemented as a WebFilter which is executed almost as soon as a request is received. I have implemented custom authentication converter and authentication manager which would extract some variables from the header and URL and use them for authentication. This is working without any problem.
Now I needed to add another variable taken from RoutePredicateRouteMapping before authentication is done. What I want exactly is to remove the WebFilter (called WebFilterChainProxy) from its current position and put it between the RoutePredicateRouteMapping and the FilteringWeHandler.
Here is how the default process goes:
ChannelOperations calls ReactorHttpHandlerAdapter which calls HttpWebHandlerAdapter, ExceptionHandlingWebHandler, and then org.springframework.web.server.handler.FilterWebHandler.
This WebHandler would invoke its filters and then call the DispatchHandler. One of those filters is the WebFilterChainProxy that does the authentication for Spring Security. So first step is removing the filter from here.
Now the DispatchHandler which is called after the filters would invoke RoutePredicateHandlerMapping, which would analyze the routes and give me the route ID that I need, and then it would call the org.springframework.cloud.gateway.handler.FilteringHandler (this is not the same FilteringHandler above), and that in turn would call the other filters of the Spring Cloud Gateway. What I want here is to invoke the filter after RoutePredicatehandlerMapping and before org.springframework.cloud.gateway.handler.FilteringHandler.
What I ended doing was the following:
I created and WebHttpHandlerBuilder that would remove WebFilterChainProxy and pass it as a parameter to a customized DispatcherHandler. Now that the filter is removed the request would pass the first layers without requiring authentication. In my customized DispatcherHandler I would invoke the RoutePredicateHandlerMapping and then pass the exchange variable to the WebFilterChainProxy to do the authentication before passing it to the org.springframework.cloud.gateway.handler.FilteringHandler, which worked perfectly!
I still think that I'm over engineering it and I hope that there is a way to do it using annotations and configuration beans instead of all these customized classes (WebHttpHandlerBuilder and DispatcherHandler).
You should probably implement that security filter as a proper GatewayFilter, since only those are aware of the other GatewayFilter instances and can be ordered accordingly. In your case, you probably want to order it after the routing one.
Also, please don't cross-post, the Spring team is actively monitoring StackOverflow.
I had a similar problem. The accepted solution, while interesting, was a bit drastic for me. I was able to make it work simply by adding my custom filter before SecurityWebFiltersOrder.AUTHENTICATION in the security configuration. This is similar to what I've done with success in a regular Spring mvc application.
Here's an example using oauth authentication. tokenIntrospector is my custom introspector, and requestInitializationFilter is the filter that grabs the tenant id and stashes it in the context.
#AllArgsConstructor
#Configuration
#EnableWebFluxSecurity
public class WebApiGatewaySecurityConfiguration {
private final GatewayTokenIntrospector tokenIntrospector;
private final GatewayRequestInitializationFilter requestInitializationFilter;
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
// #formatter:off
http
.formLogin().disable()
.csrf().disable()
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer.opaqueToken(c -> c.introspector(tokenIntrospector)))
.addFilterBefore(requestInitializationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
return http.build();
// #formatter:on
}
}
I am attempting to create some UI tests using SpecsFor MVC, I am coming at this from a new user's point of view in terms of testing so could be easily missing something obvious.
The site I'm testing against already uses it's own test DB so I do not need to create one. when I build and debug normally on this site it also starts up a couple of WCF projects which we use for service layer interactions. Presumably I'll need to start these in the specs config but have not got that far yet.
I've followed the documentation and have created this method:
protected override void AfterConfigurationApplied()
{
var config = new SpecsForMvcConfig();
config.UseIISExpress()
.With(Project.Named("TestSite"))
.UsePort(55555)
.CleanupPublishedFiles()
.UseMSBuildExecutableAt(#"C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe");
config.BuildRoutesUsing(MvcApplication.RegisterRoutes);
config.UseBrowser(BrowserDriver.Chrome);
_host = new SpecsForIntegrationHost(config);
_host.Start();
}
The routing of the site is set in the Global.asax hence the setting in the above method.
I also have this very basic test in place to just see if I can get it working:
protected override void When()
{
SUT.NavigateTo<HomeController>(u => u.Index());
SUT.FindLinkTo<HomeController>(u => u.About())
.Click();
}
When I debug the tests it successfully starts Chrome but hangs for ages and eventually fails with this error:
OpenQA.Selenium.WebDriverException: The HTTP request to the remote WebDriver server for URL http://localhost:49924/session/bd15d6a15395b4ca204437c340639501/element timed out after 60 seconds.
I'm not sure where that port number or session etc are coming from.
If I'm running my web project normally (outside of this whole testing project) I see a URL like this:
https://localhost:55555
I would have thought I'd see something similar for these tests cases? It doesn't really matter in any case because they're not working.
Am I missing some element of the config? Do I need to set up the WCF layer to run as well?
Here's some context: I've set up an ActiveMQBroker (version 5.13.1) with a BrokerPlugin that takes a requestId (just a UUID for tracking requests across different servers) and OAuth token (more about the OAuth token below) in the 'username' and 'password' fields, respectively, of the org.apache.activemq.command.ConnectionInfo ... it's based on this great post. On the client/consumer side, I'm wrapping the ActiveMQConnection inside of a Spring DefaultMessageListenerContainer (version 4.2.4-RELEASE) with cacheLeve=CACHE_CONSUMER (so it also caches the Session and Consumer along with the Connection).
The snag is that the client's OAuth token expires every 20 minutes, so I've set up a ScheduledExecutorService on the client side to refresh the OAuth token every 18 minutes.
My question is that if my scheduled task on the client side calls ActiveMQConnection#cleanup() followed by ActiveMQConnection#changeUserInfo(newRequestId, newAuthToken) ... will that negatively impact spring's DefaultMessageListenerContainer that is holding onto that same ActiveMQConnection? Or another way to ask, is there a "right" way for my client code to set the new "username" and "password" fields in the ActiveMQConnection without messing anything up in the DefaultMessageListenerContainer? I'm especially concerned with any multi-threading issues since the DefaultMessageListenerContainer has several consumer threads ... and my ScheduledExecutorService is, of course, running it's own thread to update the OAuth token into the ActiveMQConnection.
Would it be enough to extend DefaultMessageListenerContainer and wrap my update of the OAuth token inside a synchronized block of the 'sharedConnectionMonitor', e.g. would something the following be necessary and/or sufficient:
public class OAuthMessageListenerContainer extends DefaultMessageListenerContainer {
public void updateOAuthToken(String requestId, String authToken) throws JMSException {
synchronized(sharedConnectionMonitor) {
ActiveMQConnection amqConn = (ActiveMQConnection)getSharedConnection();
amqConn.doCleanup(true);
amqConn.changeUserInfo(requestId, authToken);
}
}
}
FWIW here's how I solved the issue:
1) I first had to upgrade ActiveMQ from 5.13.0 to 5.13.3 because 5.13.0 had a thread deadlock issue with the FailoverTransport when trying to reconnect to the broker after the transport was interrupted, e.g. network glitch, server restart, zookeeper electing a new "master" broker (we're using replicated leveldb on the server with 3 brokers)
2) After working past the thread deadlock in ActiveMQ I realized that changing the username and password on the ActiveMQConnection basically resets the whole connection anyway ... which means the DMLC is reset ... so I made a subclass of DMLC that I could reset from a TransportListener on the connection when the transport is interrupted ... but then ran into another thread deadlock in the DMLC ... and found this answer that helped me past that.
The subclass of DMLC just needs this method:
public void restart() {
try {
shutdown();
initialize();
start();
}
catch (Exception e) {
LOG.error("Could not restart DMLC", e);
}
}
and the transport listener needs to do this:
#Override
public void transportInterrupted() {
dmlc.restart();
}