Angular本地中转报错:[HPM] Error occurred while trying to proxy request

代理配置

在前端开发中了,为了解决浏览器跨域问题,一般都会使用 webpack 的 devServer.proxy 功能,来中转接口。

根据 Angular 的文档,只需要四步即可使用代理。

(1) 在 src/ 目录下创建 proxy.conf.json 配置文件.

(2) 在 proxy.conf.json 配置文件中写上中转规则:

{
  "/api": {
    "target": "http://localhost:3000",
    "secure": false
  }
}

(3) 在 angualr.json 配置文件中,加上 proxyConfig 的配置:

"architect": {
  "serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "options": {
      "browserTarget": "your-application-name:build",
      "proxyConfig": "src/proxy.conf.json"
    },

(3) 使用 ng serve 重新启动项目.

这样在Angular项目中访问 http://localhost:4200/api 就会自动中转到 http://localhost:3000/api 接口了。

莫名其妙的问题

就这样,在开发环境中一直可以正常运行,上周五重启电脑,本周一再运行居然报错了。

浏览器接口请求的报错信息如下:

Error occured while trying to proxy to: localhost:4201/adminapi/login?AdminLogin

在终端中报错如下:

[HPM] POST /adminapi/login?AdminLogin -> http://localhost:8001/
[HPM] Error occurred while trying to proxy request /adminapi/login?AdminLogin 
 from localhost:4201 to http://localhost:8001/ 
  (ECONNREFUSED) 
  (https://nodejs.org/api/errors.html#errors_common_system_errors)

在HTTP拦截器中打印的 HttpErrorResponse 错误如下:

HttpErrorResponse {
    headers: HttpHeaders, 
    status: 504, 
    statusText: "Gateway Timeout", 
    url: "http://localhost:4201/adminapi/login", 
    ok: false
}

看一下项目的配置吧。

前端使用 Angular 11, 后端使用 PHP lumen 8+,前端后端同时开发。

为了方便开发,直接使用了 PHP 内置的web服务器,没有使用 Vagrant + Homestead 环境。

$ php -S localhost:8001 -t ./public
[Mon Mar 22 17:31:34 2021] PHP 8.0.3 
  Development Server (http://localhost:8001) 
  started

接口配置如下:

// src/environments/environment.ts
export const environment = {
  production: false,
  // 当前ng启动的地址,使用proxy.conf.json配置中转到配置里的地址
  apiHost: 'http://localhost:4201/api', 
  adminApi: 'http://localhost:4201/adminapi', 
};

中转文件 src/proxy.conf.json 配置如下:

{
  "/adminapi/*": {
    "target": "http://localhost:8001/",
    "secure": false,
    "changeOrigin": true,
    "logLevel": "debug"
  },
  "/api/*": {
    "target": "http://localhost:8001/",
    "secure": false,
    "changeOrigin": true,
    "logLevel": "debug"
  }
}

网上的解决方法

(1) 配置文件 proxy.conf.json 未指定 http 或 https: 【排除,不是该问题】

# 未加 http,报错
target: 'localhost:8081',  
# 正常
target: 'http://localhost:8081', 

(2) 后端接口没启动: 【排除,不是该问题】

可以看到接口是正常的。直接在浏览器复制curl请求,在终端执行请求也是正常的。

$ curl -XPOST localhost:8001
{"Status":401,"Msg":"Unauthorized.","Data":"","metadata":{}}%

(3) proxy 配置规则不正确。 【排除,不是该问题】

查看 webpack 的 devServer.proxy 配置,也未见异常。

而且,proxy.conf.json 配置文件并未改动,之前都是正常的。

(4) 删掉 node_module 重新下载依赖包。重装了 nodejs (v15.6)。都不行。

(5) 有网友指出要将 localhost 改成 ip 访问。

✔ Compiled successfully.

[HPM] POST /adminapi/login?AdminLogin -> http://127.0.0.1:8001/
[HPM] Error occurred while trying to proxy request /adminapi/login 
from localhost:4201 to http://127.0.0.1:8001/ 
 (ECONNREFUSED) 
 (https://nodejs.org/api/errors.html#errors_common_system_errors)

也是不行。继续报错。

其实这里就有问题了,潜意识里,一直认为 localhost 就是等于 127.0.0.1 的。

$ curl -XPOST 127.0.0.1:8001
curl: (7) Failed to connect to 127.0.0.1 port 8001: Connection refused

不知为何,这里居然是访问不了的。但是 ping 查看 localhost 也是访问的 127.0.0.1 的。

$ ping localhost
PING localhost (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.061 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.255 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.181 ms
^C

这时,看到了这篇文章《一个localhost引发的血案》。

webpack 的 devServer.proxy 配置,其实是用的 http-proxy-middleware 包。

在 node_modules 找到这个包,找个跑出错误地方查看。

找到 logError 函数,可以达到

// node_modules/http-proxy-middleware/lib/index.js

function logError(err, req, res) {
var hostname =
  (req.headers && req.headers.host) || (req.hostname || req.host)
var target = proxyOptions.target.host || proxyOptions.target
var errorMessage =
  '[HPM] Error occurred while trying to proxy request %s from %s to %s (%s) (%s)'
var errReference =
  'https://nodejs.org/api/errors.html#errors_common_system_errors'
 // <------- 在这里打印日志 
 logger.error("==========> 打印日志: ")
 logger.error(err)

logger.error(
  errorMessage,
  req.url,
  hostname,
  target,
  err.code || err,
  errReference
)
}

在控制台可以看到输出的错误日志。

[HPM] Error occurred while trying to proxy request /adminapi 
  from localhost:4201 to http://127.0.0.1:8001/ 
  (ECONNREFUSED) 
  (https://nodejs.org/api/errors.html#errors_common_system_errors)
[HPM] POST /adminapi?AdminGetTransactionList -> http://127.0.0.1:8001/
==========> 打印日志:
Error: connect ECONNREFUSED 127.0.0.1:8001
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1138:16) {
  errno: -61,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 8001
}

可以看到原始日志,connect ECONNREFUSED 127.0.0.1:8001,果然是因为 127.0.0.1 无法访问的缘故。

当前问题的解决办法:

(1) 在服务端后台解决跨域问题(上线后接口配置)

比如可以使用 Homestead + Vagrant 配置后端开发环境。在 Nginx 里使用 add_header 配置 Access-Control-Allow-Origin 策略。

(2) 使用jsonp解决跨域问题。(这个在以前用jQuery开发时比较常用)

(3) 开发继续使用 PHP 内置web服务,前端继续使用代理自己解决跨域。

重启PHP后台接口,使用 ip:

$ php -S localhost:8001 -t ./public

改成:

$ php -S 127.0.0.1:8001 -t ./public

参考链接

http://blog.epoos.com/2018/05/21/proxy-error/
https://angular.io/guide/build
https://webpack.js.org/configuration/dev-server/#devserverproxy