Spring microservices SSO with ReactJS - reactjs

I'm working on single-sign-on implementation in my project with microservice architecture application, using spring-cloud-netflix. At the moment I have done with OAuth2 service and gateway service.
Here's my security config for gateway:
/**
* SSO security config.
*/
#Configuration
#EnableZuulProxy
#EnableOAuth2Sso
#EnableWebSecurity
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2ClientAuthenticationProcessingFilter ssoFilter;
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private SsoLogoutSuccessHandler logoutSuccessHandler;
/**
* SSO http config.
*
* #param http configurer
* #throws Exception exception
*/
#Override
public void configure(HttpSecurity http) throws Exception {
http
// .cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/test", "/favicon.ico", "/sockjs-node/**", "/static/**", "/*.js", "/*.jpg",
"/rest/**", "/uaa/**", "/backend/**",
"/users/**", "/files/**", "/roles/**").permitAll()
.anyRequest().authenticated().and()
// .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.logout().logoutSuccessHandler(logoutSuccessHandler)
.and()
.sessionManagement().maximumSessions(1)
.expiredUrl("/")
.maxSessionsPreventsLogin(false);
// http.addFilterAfter(ssoFilter, BasicAuthenticationFilter.class);
}
/**
* OAuth2 config.
*/
#Configuration
protected static class OAuth2Config {
#Bean
public OAuth2ClientAuthenticationProcessingFilter ssoFilter(
SsoLoginSuccessHandler ssoLoginSuccessHandler,
OAuth2ClientContext beaconOAuth2ClientContext,
RemoteTokenServices remoteTokenServices,
OAuth2ProtectedResourceDetails resourceDetails) {
OAuth2ClientAuthenticationProcessingFilter filter =
new OAuth2ClientAuthenticationProcessingFilter("/login");
filter.setRestTemplate(new OAuth2RestTemplate(resourceDetails,
beaconOAuth2ClientContext));
filter.setTokenServices(remoteTokenServices);
// filter.setAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler());
return filter;
}
//
// #Bean
// public CorsConfigurationSource corsConfigurationSource() {
// final CorsConfiguration configuration = new CorsConfiguration();
// configuration.setAllowedOrigins(ImmutableList.of("*"));
// configuration.setAllowedMethods(ImmutableList.of("HEAD",
// "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
// configuration.setAllowCredentials(true);
// configuration.setAllowedHeaders(
// ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
// final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// source.registerCorsConfiguration("/**", configuration);
// return source;
// }
}
}
gateway yaml config
security:
oauth2:
client:
preEstablishedRedirectUri: http://localhost:3000/login
registeredRedirectUri: http://localhost:3000/login
accessTokenUri: http://localhost:10035/uaa/oauth/token
userAuthorizationUri: http://localhost:10035/uaa/oauth/authorize
clientId: react_app
clientSecret: react_app_secret
useCurrentUri: true
resource:
tokenInfoUri: http://localhost:10035/uaa/oauth/check_token
I'm bit confused, how i should authorize react app client, if it running on different port (webpack dev server, port 3000)?

One alternative is to setup a proxy using webpack, for example, in your webpack configuration add it like this:
devServer: {
contentBase: '/static',
historyApiFallback: true,
port: 3000,
compress: false,
inline: false,
hot: true,
host: '0.0.0.0',
proxy: {
'/api': { // <---- Intercept all calls to `/api`
target: 'http://localhost:8080', // <--- Your API server in a different port
changeOrigin: true,
secure: false,
},
},
},
The previous configuration setup a proxy, so whenever there's a request to /api, it will proxy the request to your server running in the other port.

Related

How to configure NGINX with websocket for React app hosted in Kubernetes

The websocket (using #stomp/stompjs library) connection works ok on local development bench. Running the app on Azure AKS platform using an NGINX proxy for serving the React app (with also NGINX ingress controller) also works fine. The challenge, however, is the websocket connection in the latter environment.
Firefox browser returns the following error when trying to connect:
Uncaught (in promise) DOMException: An invalid or illegal string was specified ...
i stomp-handler.ts:31
(Async: promise callback)
s stomp-handler.ts:31
da stomp-handler.ts:31
da stomp-handler.ts:31
value client.ts:404
value client.ts:401
In React, the websocket configuration is:
const server = {
name: process.env.REACT_APP_BASE_URL_SOCKET,
}
...
const client = new Client();
client.configure({
brokerURL: server.name,
...
The environment variable is:
REACT_APP_BASE_URL_SOCKET=/ws
NGINX configuration for React app is specified as:
...
http {
...
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
# listen on port 80
listen 80;
server_name foo.com;
gzip off;
proxy_max_temp_file_size 0;
# save logs here
access_log /var/log/nginx/access.log compression;
root /usr/share/nginx/html;
index index.html index.htm;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
try_files $uri $uri/ /index.html =404;
}
location /ws {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://bar-api-service.default.svc.cluster.local:8000;
}
...
}
}
With Docker file as:
FROM nginx:alpine
COPY build/ /usr/share/nginx/html
# Copy our custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
ENTRYPOINT ["nginx", "-g", "daemon off;"]
Meanwhile, the Kubernetes NGINX Ingress controller is configured as:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foo-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/secure-backends: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/websocket-services: bar-ui-service
nginx.org/websocket-services: bar-ui-service
spec:
tls:
- hosts:
- foo.com
secretName: tls-secret
rules:
- host: foo.com
http:
paths:
- pathType: Prefix
backend:
service:
name: bar-ui-service
port:
number: 80
path: /
With "bar-ui-service" as:
apiVersion: v1
kind: Service
metadata:
name: bar-ui-service
spec:
type: NodePort
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
selector:
app: bar-ui
And "bar-api-service" as:
apiVersion: v1
kind: Service
metadata:
name: bar-api-service
spec:
selector:
app: bar-api
tier: backend
ports:
port: 8000
targetPort: 8000
In the Spring Boot API serving the websocket, Spring Security is used, with OAuth2 resource server. Configuration of HttpSecurity is:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable()
.formLogin().disable()
.and()
.cors()
.and()
.authorizeRequests()
.antMatchers("/ws").permitAll()
And websocket broker config as:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue", "/topic");
registry.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("foo.com");
}
}
How do I get the websocket to work in k8s?
To solve this challenge in Kubernetes, I had to change the WebSocket library. Simply put, the '#stomp/stompjs' library (v6.1.2) cannot upgrade from http to ws. After trying out several libraries (yes, JavaScript WebSocket libraries seem to be a dime a dozen out there!), I settled on react-stomp-hooks (v2.1.0).
For those who might be curious about the fuller solution, read on.
In React (TS), the 'react-stomp-hook' library comes with a convenient Provider component for connecting to the server. This may be added to App.tsx like so:
...
import {
StompSessionProvider,
} from "react-stomp-hooks";
import WebSocketSubscriptions from './shared/WebSocketSubscription';
...
const socketHeaders = {
login: token.userId,
passcode: <can be anything>
}
return (
<React.Fragment>
{token &&
<StompSessionProvider
url={server.name}
connectHeaders={socketHeaders}>
<WebSocketSubscriptions />
</StompSessionProvider>}
...
The url ('server.name') is from an environment variable defined as:
REACT_APP_BASE_URL_SOCKET=/ws
for Kubernetes deployment, and:
REACT_APP_BASE_URL_SOCKET=http://localhost:8000/ws
in the local development environment.
In order to work, the web socket's connection header requires to pass down a user identifier to match with Spring Security's user authentication (shown below). In my example, this is provided by the OAuth 2.0 JWT token. However, the "passcode", although a required parameter, can be anything. Note the Provider enables subscriptions to be defined in a separate component (or components) using a useSubscription hook, for example:
import {
useSubscription,
} from "react-stomp-hooks";
const WebSocketSubscriptions: React.FC = () => {
useSubscription("/app/subscribe", (message) => {
console.log(`..subscribed ${message}`);
});
...
The React-serving NGINX proxy configuration is as follows (you can see the fuller implementation in my question above):
...
location /ws {
proxy_pass http://bar-api-service.default.svc.cluster.local:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_cache_bypass $http_upgrade;
}
...
Meanwhile, the Kubernetes NGINX ingress controller need only deal with secure termination of traffic, like:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foo-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt
spec:
tls:
- hosts:
- foo.com
secretName: tls-secret
rules:
- host: foo.com
...
The heavy-lifting now comes in the Spring Boot (v2.7.3) API where the WebSocket server resides. Addition of Spring Security is what makes the solution hairy.
At least the following dependencies are required:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-messaging</artifactId>
<version>5.6.2</version>
</dependency>
First let's look at the websocket's support files:
The AuthChannelInterceptorAdaptor class authenticates the user:
#Component
public class AuthChannelInterceptorAdaptor implements ChannelInterceptor {
private static final String USERNAME_HEADER = "login";
private static final String PASSWORD_HEADER = "passcode";
private final WebSocketAuthenticatorService webSocketAuthenticatorService;
#Autowired
public AuthChannelInterceptorAdaptor(final WebSocketAuthenticatorService webSocketAuthenticatorService) {
this.webSocketAuthenticatorService = webSocketAuthenticatorService;
}
#Override
public Message<?> preSend(final Message<?> message, final MessageChannel channel)
throws AuthenticationException {
final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT == accessor.getCommand()) {
final String username = accessor.getFirstNativeHeader(USERNAME_HEADER);
final String password = accessor.getFirstNativeHeader(PASSWORD_HEADER);
final UsernamePasswordAuthenticationToken user =
webSocketAuthenticatorService.getAuthenticatedOrFail(username, password);
accessor.setUser(user);
}
return message;
}
}
As mentioned earlier, for an API server using the OAuth 2.0 Resource library, only the username need be provided; password can be anything. Spring Boot Security simply authenticates the user id (from the websocket's header login) and accepts the connection.
The authentication is carried out in the WebSocketAuthenticatorService class:
/*
courtesy: Anthony Raymond: https://stackoverflow.com/questions/45405332/websocket-authentication-and-authorization-in-spring
*/
#Component
public class WebSocketAuthenticatorService {
public UsernamePasswordAuthenticationToken getAuthenticatedOrFail(final String username,
final String password)
throws AuthenticationException {
if (username == null || username.trim().isEmpty()) {
throw new AuthenticationCredentialsNotFoundException("Username was null or empty.");
}
if (password == null || password.trim().isEmpty()) {
throw new AuthenticationCredentialsNotFoundException("Password was null or empty.");
}
// You can add your own logic for retrieving user from, say, db
//if (fetchUserFromDb(username, password) == null) {
// throw new BadCredentialsException("Bad credentials for user " + username);
// Null credentials, we do not pass the password along
return new UsernamePasswordAuthenticationToken(
username,
null,
Collections.singleton((GrantedAuthority) () -> "USER") // MUST provide at least one role
);
}
}
The WebSocketAuthorizationSecurityConfig class extends AbstractSecurityWebSocketMessageBrokerConfigurer, allowing configuration of inbound messages:
#Configuration
public class WebSocketAuthorizationSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(final MessageSecurityMetadataSourceRegistry messages) {
// You can customize your authorization mapping here.
messages.anyMessage().authenticated();
}
#Override
protected boolean sameOriginDisabled() {
return false;
}
}
Then we come to the WebSocketBrokerConfig class, which implements WebSocketMessageBrokerConfigurer:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue", "/topic");
registry.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("http://localhost:3000", "foo.com")
.withSockJS();
}
}
This in-memory broker is good for training and POCs. In production, however, a more robust provider like RabbitMQ or Kafka is called for.
Another implementation of the WebSocketMessageBrokerConfigurer is required for configuring the inbound channel. This is implemented in the class WebSocketSecurityConfig:
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
#Autowired
private AuthChannelInterceptorAdaptor authChannelInterceptorAdapter;
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
// Endpoints are already registered on WebSocketConfig, no need to add more.
}
#Override
public void configureClientInboundChannel(final ChannelRegistration registration) {
registration.setInterceptors(authChannelInterceptorAdapter);
}
}
Note the #Order annotation, which configures a high precedence for the channel.
The React app client move from a ws-based websocket connection to an http-brokered one also requires a CORS filter to work. The example filter looks like:
/*
courtesy of https://stackoverflow.com/users/3669624/cнŝdk
*/
#Component
public class CORSFilter implements Filter {
private final List<String> allowedOrigins = Arrays.asList(
"http://localhost:3000", "foo.com");
public void destroy() {}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// Lets make sure that we are working with HTTP (that is, against HttpServletRequest and HttpServletResponse objects)
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// Access-Control-Allow-Origin
String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", allowedOrigins.contains(origin) ? origin : "");
response.setHeader("Vary", "Origin");
// Access-Control-Max-Age
response.setHeader("Access-Control-Max-Age", "3600");
// Access-Control-Allow-Credentials
response.setHeader("Access-Control-Allow-Credentials", "true");
// Access-Control-Allow-Methods
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
// Access-Control-Allow-Headers
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, " + "X-CSRF-TOKEN");
}
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {
}
}
The WebSocketController class simply provides an endpoint for the websocket's subscriptions and support for discrete '/queues'.
#Controller
public class WebSocketController {
#MessageMapping("/subscribe")
#SendToUser("/queue/notification")
public String replyToAccountFromClient(#Payload String message,
Principal user) {
return String.format("hello: %s", message);
}
#MessageExceptionHandler
#SendTo("/queue/errors")
public String handleException(Throwable exception) {
return exception.getMessage();
}
}
Finally, the HttpSecurity configuration looks like the following:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable()
.formLogin().disable()
.addFilterAt(corsFilter, BasicAuthenticationFilter.class)
.csrf().ignoringAntMatchers(API_URL_PREFIX)
.and()
.cors()
.and()
.authorizeRequests()
.antMatchers("/ws/**").permitAll()
...
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer.jwt(jwt ->
jwt.jwtAuthenticationConverter(
getJwtAuthenticationConverter())))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

React setupProxy not proxying request

I’m trying to setup a React web application using an asp.net core web API in two separate projects. I followed Microsoft’s guide for setting up the templates using two separate visual studio projects - Create an ASP.NET Core app with React - https://learn.microsoft.com/en-us/visualstudio/javascript/tutorial-asp-net-core-with-react?view=vs-2022
Out of the box, everything works and I am able to proxy my api request to the sample /weatherforecast controller’s get method. Next, I created my own controller and now I don’t seem to be able to proxy my request to this controller.
Making a request to https://localhost:3000/api/employee just returns 404. I also see a message in my console about no matching route.
I've tried various patterns for the context all with no luck. I would think that '/api' is all I need.
My setupProxy.js file looks like this.
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
['/api/weatherforecast', '/api/employee'],
createProxyMiddleware({
target: 'https://localhost:7157',
secure: false,
})
); };
My launch settings.json in my web api project
{
"profiles": {
"WebApi": {
"commandName": "Project",
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7157;http://localhost:5157"
},
"IIS Express": {
"commandName": "IISExpress",
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:34955",
"sslPort": 44352
}
}
}
Partial of My Employee Controller
namespace WebApi.Controllers {
[ApiController]
[Route("api/[controller]")]
public class EmployeeController : Controller
{
private readonly ILogger<EmployeeController> logger;
private readonly Context context;
public EmployeeController(ILogger<EmployeeController> logger, TenthContext context)
{
this.logger = logger;
this.context = context;
}
// GET: EmployeeController
[HttpGet(Name="GetEmployee")]
public IEnumerable<Employee> Get()
{
var employees = context.Employees.ToList();
return employees;
}
Not sure what the deal was but all worked as expected after I rebooted.

webpack-dev-server set cookie via proxy

We have setup our development environment with webpack-dev-server. We use its proxy config to communicate with the backend.
We have a common login page in the server which we use in all our applications. We it is called, it sets a session cookie which expected to passed with subsequent requests. We have used the following config but the cookie is not set in the browser for some reason. I can see it in response header in the network tab of dev tool.
const config = {
devServer: {
index: "/",
proxy: {
"/rest_end_point/page": {
target: "https://middleware_server",
secure : false
},
"/": {
target: "https://middleware_server/app/login",
secure : false
},
}
The https://middleware_server/app/login endpoint returns the login page with the set-cookie header.
The proxy is used to avoid CORS errors when accessing login pages and API calls.
Upto this point no code from the application is executed. Do we have to do something in the coomon login page to get the cookie set?
the application is written with React.
Any help would be appreciated.
I have the same use case and this is what I have done.
In my case, I have multiple proxy targets so I have configured the JSON (ProxySession.json) accordingly.
Note: This approach is not dynamic. you need to get JSESSIONID manually(session ID) for the proxy the request.
login into an application where you want your application to proxy.
Get the JSESSIONID and add it in JSON file or replace directly in onProxyReq function and then run your dev server.
Example:
Webpack-dev.js
// Webpack-dev.js
const ProxySession = require("./ProxySession");
config = {
output: {..........},
plugins: [.......],
resolve: {......},
module: {
rules: [......]
},
devServer: {
port: 8088,
host: "0.0.0.0",
disableHostCheck: true,
proxy: {
"/service/**": {
target: ProxySession.proxyTarget,
changeOrigin: true,
onProxyReq: function(proxyReq) {
proxyReq.setHeader("Cookie", "JSESSIONID=" + ProxySession[buildType].JSESSIONID + ";msa=" + ProxySession[buildType].msa + ";msa_rmc=" + ProxySession[buildType].msa_rmc + ";msa_rmc_disabled=" + ProxySession[buildType].msa_rmc);
}
},
"/j_spring_security_check": {
target: ProxySession.proxyTarget,
changeOrigin: true
},
"/app_service/websock/**": {
target: ProxySession.proxyTarget,
changeOrigin: true,
onProxyReq: function(proxyReq) {
proxyReq.setHeader("Cookie", "JSESSIONID=" + ProxySession[buildType].JSESSIONID + ";msa=" + ProxySession[buildType].msa + ";msa_rmc=" + ProxySession[buildType].msa_rmc + ";msa_rmc_disabled=" + ProxySession[buildType].msa_rmc);
}
}
}
}
ProxySession.json
//ProxySession.json
{
"proxyTarget": "https://t.novare.me/",
"build-type-1": {
"JSESSIONID": "....",
"msa": "....",
"msa_rmc": ...."
},
"build-type-2": {
"JSESSIONID": ".....",
"msa": ".....",
"msa_rmc":"....."
}
}
I met the exact same issue, and fixed it by this way:
This is verified and worked, but it's not dynamic.
proxy: {
'/my-bff': {
target: 'https://my.domain.com/my-bff',
changeOrigin: true,
pathRewrite: { '^/my-bff': '' },
withCredentials: true,
headers: { Cookie: 'myToken=jx42NAQSFRwXJjyQLoax_sw7h1SdYGXog-gZL9bjFU7' },
},
},
To make it dynamic way, you should proxy to the login target, and append a onProxyRes to relay the cookies, something like: (not verified yet)
onProxyRes: (proxyRes: any, req: any, res: any) => {
Object.keys(proxyRes.headers).forEach(key => {
res.append(key, proxyRes.headers[key]);
});
},
"/api/**": {
...
cookieDomainRewrite: { "someDomain.com": "localhost" },
withCredentials: true,
...
}
You can use this plugin to securely manage auth cookies for webpack-dev-server:
A typical workflow would be:
Configure a proxy to the production service
Login on the production site, copy authenticated cookies to the local dev server
The plugin automatically saves your cookie to system keychain
https://github.com/chimurai/http-proxy-middleware#http-proxy-options
use option.cookieDomainRewrite and option.cookiePathRewrite now
cookies ??
devServer: {
https: true, < ------------ on cookies
host: "127.0.0.1",
port: 9090,
proxy: {
"/s": {
target: "https://xx < --- https
secure: false,
//pathRewrite: { "^/s": "/s" },
changeOrigin: true,
withCredentials: true
}
}
}
. . . . . . . . . . .

Ensure only one TCP connection using Netty4Http

I am trying to send a large number of HTTPS requests using Netty4Http component.
Here is a sample code to test this:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.component.netty4.http.NettyHttpComponent;
import org.apache.camel.impl.DefaultCamelContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataWriter {
private static final Logger LOG = LoggerFactory.getLogger(DataWriter.class);
private static DataWriter dataWriter;
private final CamelContext context;
private final Map<String, Object> headers = new HashMap<>();
// private NettyHttpEndpoint nettyHttpEndpoint;
private ProducerTemplate template;
private String endpoint;
public static void createDataWriterInstance() {
if (dataWriter == null) {
dataWriter = new DataWriter();
}
}
public static DataWriter getDataWriterInstance() {
return dataWriter;
}
private DataWriter() {
this.context = new DefaultCamelContext();
try {
final NettyHttpComponent nettyHttpComponent = this.context.getComponent("netty4-http",
org.apache.camel.component.netty4.http.NettyHttpComponent.class);
this.context.addComponent("fcpnettyhttpComponent", nettyHttpComponent);
this.template = this.context.createProducerTemplate();
this.headers.put("Content-Type", "application/json");
this.headers.put("Connection", "keep-alive");
this.headers.put("CamelHttpMethod", "POST");
String trustCertificate = "&ssl=true&passphrase=" + "123456" + "&keyStoreFile="
+ "C:/Users/jpisaac/certs/publicKey.store"
+ "&trustStoreFile=C:/Users/jpisaac/certs/publicKey.store" ;
this.endpoint = "netty4-http:"+ "https://xx.xx.xx.xx:8443/server"
+ "?useByteBuf=true&disableStreamCache=true&connectTimeout=30000&requestTimeout=30000&reuseChannel=true"
+ "&keepAlive=true&tcpNoDelay=true&sync=false&reuseAddress=true&sendBufferSize=1000"
+ trustCertificate;
this.template.start();
this.context.start();
} catch (final Exception e) {
LOG.error("Exception while starting Camel context ", e);
}
}
public void sendData(final String message) {
try {
CompletableFuture<Object> future=this.template.asyncRequestBodyAndHeaders(this.endpoint, message, this.headers);
System.err.println("Sent data "+message);
} catch (final CamelExecutionException e) {
LOG.error("Error while sending data", e);
}
}
public void stop() {
try {
this.template.stop();
this.context.stop();
} catch (final Exception e) {
LOG.error("Exception while stopping Camel context ", e);
}
}
public static void main(String[] args) throws Exception {
createDataWriterInstance();
DataWriter test = getDataWriterInstance();
int i = 0;
while (i<50) {
test.sendData("Hello " + i++);
}
while (true) {}
}
}
This code works, however we see that multiple ports are opened while sending the data in async mode. This can be verified by checking on Wireshark.
Also when we analyze the program on JVisualVM we can see that several NettyClientTCPWorker and ProducerTemplate threads are created. I see that we can control the number of worker threads by the workerCount setting.
I have a restraint on the number of ports that I open on my client machine to send data to the server. I will need to keep it at a configurable value(usually 1).
How can I ensure that only one port is opened on the client machine and still use the async mode?
I tried setting the producerPoolMaxActive property to 1. Now only one port gets opened, but that also means that only one request is sent. Looks like a port is opened for each request that is sent. This is something I need to avoid.
[Update] I have added Connection: keep-alive in the headers, but that did not help. I think the core issue is that a new connection is being opened for each request. I see this in the logs:
2017-07-03 11:25:32.367 [Camel (camel-1) thread #0 - ProducerTemplate] DEBUG o.a.c.component.netty4.NettyProducer.openConnection(NettyProducer.java:436) - Created new TCP client bootstrap connecting to 10.194.242.10:8443 with options: Bootstrap(BootstrapConfig(group: NioEventLoopGroup, channelFactory: NioSocketChannel.class, options: {SO_KEEPALIVE=true, TCP_NODELAY=true, SO_REUSEADDR=true, CONNECT_TIMEOUT_MILLIS=30000}, handler: org.apache.camel.component.netty4.http.HttpClientInitializerFactory#4d814f1e, resolver: io.netty.resolver.DefaultAddressResolverGroup#488dff6f))
2017-07-03 11:25:32.366 [Camel (camel-1) thread #4 - ProducerTemplate] DEBUG o.a.c.component.netty4.NettyProducer.openConnection(NettyProducer.java:436) - Created new TCP client bootstrap connecting to 10.194.242.10:8443 with options: Bootstrap(BootstrapConfig(group: NioEventLoopGroup, channelFactory: NioSocketChannel.class, options: {SO_KEEPALIVE=true, TCP_NODELAY=true, SO_REUSEADDR=true, CONNECT_TIMEOUT_MILLIS=30000}, handler: org.apache.camel.component.netty4.http.HttpClientInitializerFactory#4d814f1e, resolver: io.netty.resolver.DefaultAddressResolverGroup#488dff6f))
2017-07-03 11:25:32.366 [Camel (camel-1) thread #3 - ProducerTemplate] DEBUG o.a.c.component.netty4.NettyProducer.openConnection(NettyProducer.java:436) - Created new TCP client bootstrap connecting to 10.194.242.10:8443 with options: Bootstrap(BootstrapConfig(group: NioEventLoopGroup, channelFactory: NioSocketChannel.class, options: {SO_KEEPALIVE=true, TCP_NODELAY=true, SO_REUSEADDR=true, CONNECT_TIMEOUT_MILLIS=30000}, handler: org.apache.camel.component.netty4.http.HttpClientInitializerFactory#4d814f1e, resolver: io.netty.resolver.DefaultAddressResolverGroup#488dff6f))
2017-07-03 11:25:32.367 [Camel (camel-1) thread #1 - ProducerTemplate] DEBUG o.a.c.component.netty4.NettyProducer.openConnection(NettyProducer.java:436) - Created new TCP client bootstrap connecting to 10.194.242.10:8443 with options: Bootstrap(BootstrapConfig(group: NioEventLoopGroup, channelFactory: NioSocketChannel.class, options: {SO_KEEPALIVE=true, TCP_NODELAY=true, SO_REUSEADDR=true, CONNECT_TIMEOUT_MILLIS=30000}, handler: org.apache.camel.component.netty4.http.HttpClientInitializerFactory#4d814f1e, resolver: io.netty.resolver.DefaultAddressResolverGroup#488dff6f))
2017-07-03 11:25:32.366 [Camel (camel-1) thread #2 - ProducerTemplate] DEBUG o.a.c.component.netty4.NettyProducer.openConnection(NettyProducer.java:436) - Created new TCP client bootstrap connecting to 10.194.242.10:8443 with options: Bootstrap(BootstrapConfig(group: NioEventLoopGroup, channelFactory: NioSocketChannel.class, options: {SO_KEEPALIVE=true, TCP_NODELAY=true, SO_REUSEADDR=true, CONNECT_TIMEOUT_MILLIS=30000}, handler: org.apache.camel.component.netty4.http.HttpClientInitializerFactory#4d814f1e, resolver: io.netty.resolver.DefaultAddressResolverGroup#488dff6f))
Add HTTP header Connection: keep-alive, otherwise the server should close the connection after each request.

asp.net core Index.html not shows as default?

namespace MyQuotesApp
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultFiles();
app.UseStaticFiles();
//app.Run(async (context) =>
//{
// await context.Response.WriteAsync("Hello World!");
//});
app.UseMvc();
}
}
}
UseDefaultFiles pick these files by default.
default.htm
default.html
index.htm
index.html
If that not worked in your case. You can specify name of your default file with DefaultFilesOptions.
DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("index.html");
app.UseDefaultFiles(options);
You can also use app.UseFileServer();, it combines the functionality of
app.UseDefaultFiles();
app.UseStaticFiles();
Note: UseDefaultFiles must be called before UseStaticFiles to serve the default file. UseDefaultFiles is a URL re-writer that doesn't actually serve the file. You must enable the static file middleware (UseStaticFiles) to serve the file.
P.S. also update your packages to most latest.
Try this.
app.UseMvc(config =>
{
config.MapRoute(
name: "Default",
template: "{controller=*YourControllerName*}/{action=Index}/{id?}"
);
});

Resources