I'm using BrowserSync in a somewhat strange setup, where I'm proxying my web server (Apache in a Docker container), but also serving hot module replacement (HMR) from a Webpack dev server.
In my local dev environment, the setup looks something like this:
https://mytestsite.localhost – an Apache service in a Docker container
https://localhost:8888 – Webpack Dev server, serving HMR
https://localhost:3000 – BrowserSync`
For hard reloads, this all works fine – the webpack dev server appears to pass on the message that a reload is needed and all is well.
The issue that I'm having is with hot reloads. The document being served by the BS proxy should be reading the hotupdate.json served by webpack-dev-server. On receiving a hot update, the page tries to load /hotupdate.json (which I believe tells it which snippet of code to pick up), however, because it's a relative URL, the browser tries to GET https://localhost:3000/hotupdate.json, which 404s, because this hotupdate.json is actually served by the Webpack server, e.g. https://localhost:8888/hotupdate.json.
Because I know the absolute URL to this resource, I'd like to force BrowserSync to redirect any requests to /hotupdate.json to https://localhost:8888/hotupdate.json. I thought I could do this with some middleware, but I'm struggling, possibly because I've never fully groked Express-style middleware.
I've tried something like this, but no worky!
browserSync({
proxy: {
target: `https://${process.env.APP_HOST_PATH}`,
middleware: [
dontProxyHotUpdate,
require('webpack-dev-middleware')(bundler, {
noInfo: true,
publicPath: webpackConfig.output.path
}),
]
},
files: [
'app/css/*.css',
'app/*.html'
]
});
function dontProxyHotUpdate (req, res, next){
if(req.url === '/hotupdate.json'){
req.url = 'https://127.0.0.1:8888/hotupdate.json';
}
next();
}
It definitely loads the middleware as I can, say, console.log(req.url), but I'm not able to rewrite the request URL. I suppose possible solutions would either be to rewrite the request URL, or to overwrite the response directly.
N.B. One might ask why I'm not using webpack-dev-server directly, as it serves HMR nicely on its own. It does, but it also doesn't allow for nice rewriting of the anchor elements within a page, e.g. changing https://mytestsite.localhost/link to https://localhost:3000/link. This is important obviously for navigating through a site while developing (which is nice, but not essential), but even more important for rewriting links to assets – SVGs, in particular, which won't load unless the path, host and port all match.
Well, in the end I sorted my own problem!
I ended up writing my own middleware using http-proxy-middlware - like this.
var proxy = require('http-proxy-middleware');
browserSync({
proxy: {
target: `https://${process.env.APP_HOST_PATH}`,
middleware: [
dontProxyHotUpdate,
require('webpack-dev-middleware')(bundler, {
noInfo: true,
publicPath: webpackConfig.output.path
}),
// require("webpack-hot-middleware")(bundler) // I don't think that we want this here as it can be handled by the webpack dev server
],
},
// no need to watch '*.js' here, webpack will take care of it for us,
// including full page reloads if HMR won't work
files: [
path.join(source, '**/*.php'),
path.join(source, 'style.css')
]
});
var dontProxyHotUpdate = proxy('/hotupdate*', {
target: 'https://127.0.0.1:8888/',
changeOrigin: true, // for vhosted sites, changes host header to match to target's host
logLevel: 'debug',
secure: false
});
Related
In older versions of webpack-dev-server (i.e 3 and below), we can simply expose webpack and webpack-dev-server's websock using ngrok tunnels by setting the value of devServer.public, as shown below:
// WDS v3
devServer: {
public: 'react-tunnel.ngrok.io',
// other webpack devServer config
}
This is particularly useful in developing apps in frameworks like React, as we can create a secure tunnel from the local machine, to a public URL to allow the web application to be easily tested.
However, in the latest versions of webpack-dev-server (v4), the public property has been replaced in favour client.webSocketURL object (source). I am unable to find any resources to make this work with ngrok, as the websockets are not properly connected when I point the devServer.client.webSocketURL.hostname to my ngrok tunnel.
// WDS v4
devServer: {
client: {
webSocketURL: {
hostname: 'react-tunnel.ngrok.io',
},
},
}
The above implementation is only a partial solution, as the websocket was not properly set up, thus it does not hot-reload.
Does anyone know what is the solution to this, such that ngrok will once again work well with webpack-dev-server?
You also need to provide port and protocol to make it work.
For example,
devServer: {
client: {
webSocketURL: {
hostname: 'react-tunnel.ngrok.io',
port: 0,
protocol: 'wss',
},
},
}
With above settings, your website may work in ngrok tunnel, but wont work in localhost.
To connect to the web socket regardless of url, you can set webSocketURL to auto://0.0.0.0:0/ws, which uses protocol, hostname, and port from browser. doc
This should fix hot-reloading problem as well.
devServer: {
client: {
webSocketURL: 'auto://0.0.0.0:0/ws'
},
}
I'm not sure if this is the complete solution, but this works for me. You can put the hostname as localhost and some port as your needs. After running the dev-server, you could expose the port using ngrok http {port}.
Example: ngrok http 3000.
I also stumbled onto the same issue and couldn't resolve it using any of the methods mentioned above. Took a little while before I stumbled across this simple solution, but this seems to work. Hope it helps:
devServer: {
allowedHosts: [".ngrok.io"],
},
I'm trying to create a Wordpress theme development environment (more precisely migrating from gulp w/ browser-sync to webpack). Therefore I've set up a proxy pointing to my wordpress installation (also locally). When I access the webpack dev server in the browser the proxy seems to be working fine. For instance localhost:8080/sample-page shows the WordPress page localhost:8000/sample-page.
The problem is, that any links inside that page will point back to the original port. When I use the navigation on the page I get redirected back to localhost:8000/*.
With gulp, I only had to create the browser-sync task with one option being the target URL and it worked perfectly:
browserSync.init({
proxy: pjson.themeURL || "localhost:8000",
open: false
});
I have tried to change different options of the HTTP-proxy-middleware, currently, I got the following config (I also tried options like an auto rewrite, etc.):
devServer: {
publicPath: `${options.publicPath}/build/`,
proxy: {
"*": {
target: pjson.proxyURL || "http://localhost:8000",
logLevel: "debug",
secure: false,
changeOrigin: true
}
}
},
The publicPath will resolve to /wp-content/themes/theme-name/build/ (I also tried including the whole url not just the path)
I hope I can get it to run just like the browser-sync proxy did. Maybe I need to set some headers?
I appreciate all the help!
EDIT:
I have found the following code from the browser-sync source which seems to have a method responsible for replacing all the links inside the page:
https://github.com/BrowserSync/browser-sync/blob/master/packages/browser-sync/lib/server/proxy-utils.js
The Dev server seems to have the possibility to modify a request with the onProxyRes hook where I could do something like modifiying all the links, I just didn't have the time yet to try it out, so I now using browser-sync atm.
I've not found a solution yet, my current workaround is to use webpack -watch and browser-sync without webpack-dev-server, this way hot reloading won't work though :/
My webpack config plugins look currently like this:
plugins: [
new BrowserSyncPlugin({
port: pjson.themeConf.port || 3000,
proxy: pjson.themeConf.proxyURL || "localhost:8000",
files: ["**.php"]
}),
new MiniCssExtractPlugin({})
],
Thank you so much for taking the time to check out my little conundrum. This is my first StackOverflow question, so please forgive me if I accidentally leave out something important (I'll add any information that you require if you just let me know).
My issue is this: I have a personal development VM that I use to house all of my application code, including the application's server. I use PuTTY to connect to my development VM, and I forward the port that the vert.x application server runs on (which is 7443) to my localhost. Thus, when I connect to the server, I just enter:
https://localhost:7443
in the URL bar of the browser on my local machine. I also use Gulp.js to build the UI component and watch my files for changes. Currently, the gulp watch task just rebuilds Javascript and CSS bundle files that I link to in my index.html file, and I manually reload the browser whenever the bundles are done rebuilding. I recently stumbled upon Browsersync when I was researching live-reload technologies for the browser, and it looks awesome, but I cannot get it to work with my development setup.
Here is my gulp watch task:
const browserSync = require("browser-sync").create("browser-sync-server");
gulp.task("watch", () => {
console.info("=== Initializing BrowserSync Server ===");
browserSync.init({
proxy: "localhost:7443", // This is my dev server running on port 7443
open: false // I do not want a new browser to open up when I start BrowserSync
});
console.info("=== Listening for changes to reload === ");
const jsWatcher = gulp.watch(
myJsFiles,
{ awaitWriteFinish: true },
["reload-js"]
);
const templateWatcher = gulp.watch(
myHtmlFiles,
{ awaitWriteFinish: true },
["reload-templates"]
);
const lessWatcher = gulp.watch(
myLessFiles,
{ awaitWriteFinish: true },
["reload-less"]
);
});
When I start the watch task, in the console I see:
Starting 'watch'...
=== Initializing BrowserSync Server ===
=== Listening for changes to reload ===
Finished 'watch' after 126 ms
[Browsersync] Proxying: http://localhost:7443
[Browsersync] Access URLs:
Local: http://localhost:3000
External: http://192.168.1.11:3000
UI: http://localhost:3001
UI External: http://192.168.1.11:3001
I also forward ports 3000 and 3001 to my local machine so that I can access those endpoints in my browser.
I can get to the Browsersync UI just fine at port 3001 over HTTP, but when I click on the "NEW TAB" button under Local, a new tab pops, but the server never loads. It just spins and spins, until finally I get an error in Chrome that says "No data received". Does anyone have any ideas as to what the problem could be? My guess is that is has something to do with the fact that my team's application uses HTTPS for browser access, and that Browsersync needs some further configuration to work with HTTPS, but I do not know how to go about doing this.
Thank you all so much for helping me out! Please let me know if I can provide you all with any more information.
-- Tom
Edit 5/23/18:
I used openssl to generate localhost key and cert files for my development VM, and added them to my configuration for BrowserSync in my gulpfile.js.
Here is my modified config:
const proxy = require("http-proxy-middleware");
...
browserSync.init({
ui: {
port: 8080
},
https: {
key: "./conf/browsersync/localhost.key",
cert: "./conf/browsersync/localhost.crt",
},
server: {
baseDir: "./",
index: "index.html"
},
middleware: [
proxy("/api", {
target: "https://localhost:7443",
secure: false, // Do not validate SSL certs
changeOrigin: true, // Seems to be a highly recommended setting
xfwd: true,
prependPath: true, // Ensure that the API calls are prepended with the target URL
logLevel: "debug" // So that I can see verbose console output
})
],
port: 3000,
open: false
});
This has definitely gotten me farther, but it is still not quite right. Now, when I hit https://localhost:3000 in my browser, I am taken to the index page of my web application, but none of the asynchronous API calls are getting resolved correctly. In the console where I am running the gulp watch task, I see a lot of errors from HPM (the html-proxy-middleware software). Here is one example:
[HPM] Error occurred while trying to proxy request /api/settings/ from localhost:3000 to https://localhost:7443 (ECONNRESET) (https://nodejs.org/api/errors.html#errors_common_system_errors)
Also, if I open the Javascript console in my browser window for https://localhost:3000 (the BrowserSync session), I can see lots of 504 errors (Gateway Timeout). Any ideas? Thanks again so much for your time.
-- Tom
First of all, I agree, browsersync is awesome.
[Browsersync] Proxying: http://localhost:7443 Note the http instead of https. This cannot work. In your line proxy: "localhost:7443" you are not telling anything about the protocol. Try with a https:// prefix. Most likely you'll also need the "secure: false" or similar if your development server uses a self-signed certificate. The proxy will have to accept this certificate, not your browser.
If your backend also uses secure-only cookies, you'll have to use https on the browsersync and client side too, or your browser will refuse the secure cookie. Here is my working development config (Gruntfile syntax) doing that:
var proxy = require('http-proxy-middleware');
[...]
options: {
port: 3000,
server: {
baseDir: './',
index: '_index.html'
},
// redirect all calls to /api to the backend
https: true, // required for secure cookie to work
middleware: [proxy('/api', {
target: "https://external.backend.server",
secure: false, // required if server uses self-signed certificate
changeOrigin: true,
xfwd: true // add X-Forwarded-* headers
})],
watchTask: true,
logConnections: true,
scrollRestoreTechnique: 'cookie',
reloadDebounce: 500,
ghostMode: false
}
I am trying to make an API call through Axios in my React Application. However, I am getting this CORS issue on my browser. I am wondering if i can resolve this issue from a client side as i dont have any access to the API internally. Attached is my code.
const response = axios({
method: "post",
dataType: "jsonp",
url: "https://awww.api.com",
data: {
appToken: "",
request: {
applicationName: "ddfdf",
userName: "jaime#dfd.com",
password: "dfd",
seasonIds: [1521ddfdfd5da02],
},
},
});
return {
type: SHARE_REVIEW,
payload: "response",
};
Attached is my WebPack.config.js
module.exports = {
entry: ["./src/index.js"],
output: {
path: __dirname,
publicPath: "/",
filename: "bundle.js",
},
module: {
loaders: [
{
exclude: /node_modules/,
loader: "babel",
query: {
presets: ["react", "es2015", "stage-1"],
},
},
{ test: /\.json$/, loader: "json-loader" },
],
},
resolve: {
extensions: ["", ".js", ".jsx"],
},
devServer: {
historyApiFallback: true,
contentBase: "./",
},
node: {
dns: "mock",
net: "mock",
},
};
the simplest way what I found from a tutorial of "TraversyMedia" is that
just use https://cors-anywhere.herokuapp.com in 'axios' or 'fetch' api
https://cors-anywhere.herokuapp.com/{type_your_url_here}
e.g.
axios.get(`https://cors-anywhere.herokuapp.com/https://www.api.com/`)
and in your case edit url as
url: 'https://cors-anywhere.herokuapp.com/https://www.api.com',
The ideal way would be to add CORS support to your server.
You could also try using a separate jsonp module. As far as I know axios does not support jsonp. So I am not sure if the method you are using would qualify as a valid jsonp request.
There is another hackish work around for the CORS problem. You will have to deploy your code with an nginx server serving as a proxy for both your server and your client.
The thing that will do the trick us the proxy_pass directive. Configure your nginx server in such a way that the location block handling your particular request will proxy_pass or redirect your request to your actual server.
CORS problems usually occur because of change in the website domain.
When you have a singly proxy serving as the face of you client and you server, the browser is fooled into thinking that the server and client reside in the same domain. Ergo no CORS.
Consider this example.
Your server is my-server.com and your client is my-client.com
Configure nginx as follows:
// nginx.conf
upstream server {
server my-server.com;
}
upstream client {
server my-client.com;
}
server {
listen 80;
server_name my-website.com;
access_log /path/to/access/log/access.log;
error_log /path/to/error/log/error.log;
location / {
proxy_pass http://client;
}
location ~ /server/(?<section>.*) {
rewrite ^/server/(.*)$ /$1 break;
proxy_pass http://server;
}
}
Here my-website.com will be the resultant name of the website where the code will be accessible (name of the proxy website).
Once nginx is configured this way. You will need to modify the requests such that:
All API calls change from my-server.com/<API-path> to my-website.com/server/<API-path>
In case you are not familiar with nginx I would advise you to go through the documentation.
To explain what is happening in the configuration above in brief:
The upstreams define the actual servers to whom the requests will be redirected
The server block is used to define the actual behaviour of the nginx server.
In case there are multiple server blocks the server_name is used to identify the block which will be used to handle the current request.
The error_log and access_log directives are used to define the locations of the log files (used for debugging)
The location blocks define the handling of different types of requests:
The first location block handles all requests starting with / all these requests are redirected to the client
The second location block handles all requests starting with /server/<API-path>. We will be redirecting all such requests to the server.
Note: /server here is being used to distinguish the client side requests from the server side requests. Since the domain is the same there is no other way of distinguishing requests. Keep in mind there is no such convention that compels you to add /server in all such use cases. It can be changed to any other string eg. /my-server/<API-path>, /abc/<API-path>, etc.
Even though this technique should do the trick, I would highly advise you to add CORS support to the server as this is the ideal way situations like these should be handled.
If you wish to avoid doing all this while developing you could for this chrome extension. It should allow you to perform cross domain requests during development.
Temporary solve this issue by a chrome plugin called CORS. Btw backend server have to send proper header to front end requests.
Another way besides #Nahush's answer, if you are already using Express framework in the project then you can avoid using Nginx for reverse-proxy.
A simpler way is to use express-http-proxy
run npm run build to create the bundle.
var proxy = require('express-http-proxy');
var app = require('express')();
//define the path of build
var staticFilesPath = path.resolve(__dirname, '..', 'build');
app.use(express.static(staticFilesPath));
app.use('/api/api-server', proxy('www.api-server.com'));
Use "/api/api-server" from react code to call the API.
So, that browser will send request to the same host which will be
internally redirecting the request to another server and the browser will feel that It is coming from the same origin ;)
You can have your React development server proxy your requests to that server. Simply send your requests to your local server like this: url: "/"
And add the following line to your package.json file
"proxy": "https://awww.api.com"
Though if you are sending CORS requests to multiple sources, you'll have to manually configure the proxy yourself
This link will help you set that up Create React App Proxying API requests
You can set up a express proxy server using http-proxy-middleware to bypass CORS:
const express = require('express');
const proxy = require('http-proxy-middleware');
const path = require('path');
const port = process.env.PORT || 8080;
const app = express();
app.use(express.static(__dirname));
app.use('/proxy', proxy({
pathRewrite: {
'^/proxy/': '/'
},
target: 'https://server.com',
secure: false
}));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'index.html'));
});
app.listen(port);
console.log('Server started');
From your react app all requests should be sent to /proxy endpoint and they will be redirected to the intended server.
const URL = `/proxy/${PATH}`;
return axios.get(URL);
you must be missing Cors support on your server side code. In Asp.net core you can do it following way.
Add the following call in public void ConfigureServices(IServiceCollection services)
in Startup.cs file.
public void ConfigureServices(IServiceCollection services)
{
app.UseCors(options =>
options.WithOrigins("http://localhost:3000").AllowAnyHeader().AllowAnyMethod());
}
I had the issue and I was in a dev environment (localhost) so my client and server origins where different...
so finally I overcame the problem by writing a simple custom proxy server that runs on localhost, (because I didn't had access to the server code, and I needed to allow cors from the responding origin)
Simple proxy server that runs on localhost:3001:
const express = require("express");
const { createProxyMiddleware } = require("http-proxy-middleware");
var cors = require("cors");
const app = express();
const corsOptions = {
// origin: ["http://localhost:3000"],
origin: true,
credentials: true,
};
app.use(cors(corsOptions));
const proxyMiddleware = createProxyMiddleware("/", {
target: "https://....api origin.....com",
changeOrigin: true,
});
app.use(proxyMiddleware);
app.listen(3001, () => {
console.log("proxy is listening on port 3001");
});
note that my react app is running on port 3000 and my proxy server is on port 3001
because we're using credentials in our request we need to also set origin to our app's origin to our white list, otherwise cors sets "Access-Control-Allow-Origin" to "*" which causes in-browser security error.
react sample login post request through my proxy:
axios.post("http://localhost:3001/Login", dataToPost, { withCredentials: true })
.then((res) => {
if (res.status === 200) {
//all cookies are set in you're browser
console.log(res);
}
})
.catch((err) => {
console.log(err);
});
If you trying to do something like fetch an 3rd party api and you're getting CORS error from the client-side, you can try to do using this extension:
Allow CORS: Access-Control-Allow-Origin
For chrome: https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf
For mozilla: https://addons.mozilla.org/pt-BR/firefox/addon/access-control-allow-origin/
As #Nahush already pointed out, this is actually a server issue and not something that should be handled on the client side. The server is supposed to add the headers for Access-Control-Allow-Origin:
Reference - https://www.w3.org/wiki/CORS_Enabled#How_can_I_participate.3F
Here, I am just adding an easier way to do this on the server side if your server uses express framework. Using the express CORS Middleware is a 2 line code solution for this.
install using -
npm install -S cors
Add the following code to your backend app.
import cors from 'cors';
app.use(cors());
And done.
I did use 2 solutions to dealing with it:
Using http-proxy-middleware. However, please do following below steps on video, instead of read me of library, https://youtu.be/4B5WgTiKIOY . (I did mention the weakness if you use this solution also)
Use Firebase functions to create middleware, not too complicated. I will update detail then here also.
Please let me know if you have any question.
I created an Aurelia application using aurelia-cli.
I need to disable the browsersync option and also the notifications generated by it. Like "Connected to browser sync" Like that.
Is there any way to do that ?
When you build your application for production, it will not include Browser Sync.
Instead of the normal
> au run --watch
You'll use
> au build --env prod
After that, you'll need to serve up your application through a traditional web server. If you've correctly bundled it, you'll only need index.html and your scripts folder.
just as a dev side note:
browsersync options are available at localhost:3001 (unless you changed default port)
there you can do things like turn off mirroring clicks and scrolls in all instances of your locally running app (useful when you would like to open two or more different routes in your local app in multiple tabs/browsers) or monitor history of all routes you have visited in your local app.
You can also disable the ghost mode in aurelia_project/tasks/run.js.
Just add ghostMode: false to the browserSync configuration.
let serve = gulp.series(
build,
done => {
browserSync({
online: false,
open: false,
port: 9000,
logLevel: 'silent',
ghostMode: false,
server: {
baseDir: ['.'],
middleware: [historyApiFallback(), function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
next();
}]
}
}, function(err, bs) {
let urls = bs.options.get('urls').toJS();
log(`Application Available At: ${urls.local}`);
log(`BrowserSync Available At: ${urls.ui}`);
done();
});
}
);
This disables the synchronization of interaction events between browsers. At least for me, this is often the most annoying part.