평소처럼 리액트 작업을 하던 중, 집에서만 잘만 돌아가던 react가 학교에서 실행을 하니 다음과 같은 오류를 내뿜으며 작동하기를 거부했다.
구글에 Invalid options object와 관련된 에러라면서 proxy가 연결할 서버를 찾지 못 한 것이 이유라고 생각했다.
그래서 http-proxy-middleware을 설치해서 setupProxy.js에 module.exports를 정의해주었는데 여전히 실행이 되질 않았다.
정확히는 실행이 되는데 또 다른 오류가 여전히 발생했다.
즉, 엉뚱한 부분에서 문제를 수정하려고 했기 때문에 발생했기 때문이라 생각해서 이번에는 options.allowedHosts[0] should be a non-empty string이라는 문구에 집중해보았다.
allowedHosts라는 변수가 어딘가 존재할 것이고 이를 역추적해보니 node_module 디렉토리에서 발견했다.
// node_modules/react-scripts/config/webpackDevServer.config.js
module.exports = function (proxy, allowedHost) {
const disableFirewall =
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true';
return {
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
// websites from potentially accessing local content through DNS rebinding:
// https://github.com/webpack/webpack-dev-server/issues/887
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
// However, it made several existing use cases such as development in cloud
// environment or subdomains in development significantly more complicated:
// https://github.com/facebook/create-react-app/issues/2271
// https://github.com/facebook/create-react-app/issues/2233
// While we're investigating better solutions, for now we will take a
// compromise. Since our WDS configuration only serves files in the `public`
// folder we won't consider accessing them a vulnerability. However, if you
// use the `proxy` feature, it gets more dangerous because it can expose
// remote code execution vulnerabilities in backends like Django and Rails.
// So we will disable the host check normally, but enable it if you have
// specified the `proxy` setting. Finally, we let you override it if you
// really know what you're doing with a special environment variable.
// Note: ["localhost", ".localhost"] will support subdomains - but we might
// want to allow setting the allowedHosts manually for more complex setups
allowedHosts: disableFirewall ? 'all' : [allowedHost],
웹팩과 Node.js 관련해서는 아직 공부하질 않아서 잘 모르겠지만, 분명한 건 disableFirewall은 방화벽의 on/off 여부를 묻는 것 같다.
찾아보니 process.env는 서버의 환경변수를 의미한다고 하니, 현재 인터넷이 연결된 서버의 방화벽(?) 관련 여부를 되묻고 있다.
여기서 proxy관련 이슈냐, DANGEROUSLY_DISABLE_HOST_CHECK 관련 이슈냐로 갈리는 듯 하다.
(물론 더 있겠지만 모르겠다.)
// node_modules/react-scripts/scripts/start.js
const createDevServerConfig = require('../config/webpackDevServer.config');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `choosePort()` Promise resolves to the next free port.
return choosePort(HOST, DEFAULT_PORT);
})
.then(port => {
(...)
const urls = prepareUrls(
protocol,
HOST,
port,
paths.publicUrlOrPath.slice(0, -1)
);
(...)
const serverConfig = {
...createDevServerConfig(proxyConfig, urls.lanUrlForConfig),
host: HOST,
port,
};
(...)
start.js로 와보면 createDevServerConfig의 인자로 proxyConfig와 urls.lanUrlForConfig를 인자로 받고 있는데, 후자가 바로 allowedHost였으므로 urls 변수를 확인해보면 prepareUrls라는 함수로써 정의하고 있다.
그럼 prepareUrls 함수를 까보자.
// node_modules/react-dev-utils/WebpackDevServerUtils.js
function prepareUrls(protocol, host, port, pathname = '/') {
prepareUrls 함수는 4가지 인자를 받고 있는데 우린 이 중에서도 lanUrlForConfig라는 속성값이 궁금하므로 밑으로 조금 내려보면 다음과 같은 코드를 확인할 수 있다.
const isUnspecifiedHost = host === '0.0.0.0' || host === '::';
let prettyHost, lanUrlForConfig, lanUrlForTerminal;
if (isUnspecifiedHost) {
prettyHost = 'localhost';
try {
// This can only return an IPv4 address
lanUrlForConfig = address.ip();
if (lanUrlForConfig) {
// Check if the address is a private ip
// https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
if (
/^10[.]|^172[.](1[6-9]|2[0-9]|3[0-1])[.]|^192[.]168[.]/.test(
lanUrlForConfig
)
) {
// Address is private, format it for later use
lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig);
} else {
// Address is not private, so we will discard it
lanUrlForConfig = undefined;
}
}
} catch (_e) {
// ignored
}
} else {
prettyHost = host;
}
처음에 뭐하는 코든가 싶었는데 주석을 잘 읽어보면 Check if the address is a private ip라고 적힌 문구가 있다.
그니까 PC의 IPv4의 주소가 비공개가 아닌 경우 LanUrlForConfig 값은 undefined가 되고, 이 값이 그대로 allowedHost한테 넘어갔던 것이다.
정의되지 않은 값이 넘어갔으므로 allowedHosts[0]이 변수로 전달되어 react 작동에 이상이 생겼던 것.
이전에는 잘만 됐던 게 왜 안 되나 했더니 학교에서 연결한 네트워크의 종류가 달라졌기 때문일 가능성이 크다(고 한다. ㅎ)
이 문제를 해결하는 건 생각보다 간단하다.
서버의 환경 여부와 관계없이 강제로 유효하다고 값을 할당해버리면 된다.