0
0

Docker上のNode.jsからaxiosでlocalhostへの通信がうまくいかない

Posted at

状況

axiosを使ってAPI通信を試したくて、docker上にlaravelのAPIサーバーを立ち上げてnode.jsの対話モードからaxiosで通信を試したところエラーが返ってきました。

Laravel

route/api.phpの中身は以下
http://localhost/api/testに接続すると { id: 1, name: 'test' }のJSONを返す単純なAPIです。

route/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;

Route::get('/test', function (Request $request) {
    return response()->json([
        'id' => 1,
        'name'=> 'test',
    ]);
});

Laravelと同じコンテナ内でnode.jsを対話モードで起動し、コンソールからaxiosで通信を試みたところエラーが返ってきました

コンソール(Node.js)
axios.get('http://localhost:8080/api/test');

Promise {
  <pending>,
  [Symbol(async_id_symbol)]: 406,
  [Symbol(trigger_async_id_symbol)]: 6
}
> Uncaught AxiosError: connect ECONNREFUSED ::1:8080
    at AxiosError.from (/usr/src/laravel-app/node_modules/axios/dist/node/axios.cjs:841:14)
    at RedirectableRequest.handleRequestError (/usr/src/laravel-app/node_modules/axios/dist/node/axios.cjs:3106:25)
    at RedirectableRequest.emit (node:events:517:28)
    at RedirectableRequest.emit (node:domain:489:12)
    at eventHandlers.<computed> (/usr/src/laravel-app/node_modules/follow-redirects/index.js:38:24)
    at ClientRequest.emit (node:events:517:28)
    at ClientRequest.emit (node:domain:489:12)
    at Socket.socketErrorListener (node:_http_client:501:9)
    at Socket.emit (node:events:517:28)
    at Socket.emit (node:domain:489:12)
    at Axios.request (/usr/src/laravel-app/node_modules/axios/dist/node/axios.cjs:4224:41)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  port: 8080,
  address: '::1',
  syscall: 'connect',
  code: 'ECONNREFUSED',
  errno: -111,
エラー全文はこちら Promise { , [Symbol(async_id_symbol)]: 406, [Symbol(trigger_async_id_symbol)]: 6 } > Uncaught AxiosError: connect ECONNREFUSED ::1:8080 at AxiosError.from (/usr/src/laravel-app/node_modules/axios/dist/node/axios.cjs:841:14) at RedirectableRequest.handleRequestError (/usr/src/laravel-app/node_modules/axios/dist/node/axios.cjs:3106:25) at RedirectableRequest.emit (node:events:517:28) at RedirectableRequest.emit (node:domain:489:12) at eventHandlers. (/usr/src/laravel-app/node_modules/follow-redirects/index.js:38:24) at ClientRequest.emit (node:events:517:28) at ClientRequest.emit (node:domain:489:12) at Socket.socketErrorListener (node:_http_client:501:9) at Socket.emit (node:events:517:28) at Socket.emit (node:domain:489:12) at Axios.request (/usr/src/laravel-app/node_modules/axios/dist/node/axios.cjs:4224:41) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { port: 8080, address: '::1', syscall: 'connect', code: 'ECONNREFUSED', errno: -111, config: { transitional: { silentJSONParsing: true, forcedJSONParsing: true, clarifyTimeoutError: false }, adapter: [ 'xhr', 'http', 'fetch' ], transformRequest: [ [Function: transformRequest] ], transformResponse: [ [Function: transformResponse] ], timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, maxBodyLength: -1, env: { FormData: [Function], Blob: [class Blob] }, validateStatus: [Function: validateStatus], headers: Object [AxiosHeaders] { Accept: 'application/json, text/plain, */*', 'Content-Type': undefined, 'User-Agent': 'axios/1.7.2', 'Accept-Encoding': 'gzip, compress, deflate, br' }, method: 'get', url: 'http://localhost:8080/api/test', data: undefined }, request: Writable { _writableState: WritableState { objectMode: false, highWaterMark: 16384, finalCalled: false, needDrain: false, ending: false, ended: false, finished: false, destroyed: false, decodeStrings: true, defaultEncoding: 'utf8', length: 0, writing: false, corked: 0, sync: true, bufferProcessing: false, onwrite: [Function: bound onwrite], writecb: null, writelen: 0, afterWriteTickInfo: null, buffered: [], bufferedIndex: 0, allBuffers: true, allNoop: true, pendingcb: 0, constructed: true, prefinished: false, errorEmitted: false, emitClose: true, autoDestroy: true, errored: null, closed: false, closeEmitted: false, [Symbol(kOnFinished)]: [] }, _events: [Object: null prototype] { response: [Function: handleResponse], error: [Function: handleRequestError], socket: [Function: handleRequestSocket] }, _eventsCount: 3, _maxListeners: undefined, _options: { maxRedirects: 21, maxBodyLength: Infinity, protocol: 'http:', path: '/api/test', method: 'GET', headers: [Object: null prototype], agents: [Object], auth: undefined, family: undefined, beforeRedirect: [Function: dispatchBeforeRedirect], beforeRedirects: [Object], hostname: 'localhost', port: '8080', agent: undefined, nativeProtocols: [Object], pathname: '/api/test' }, _ended: true, _ending: true, _redirectCount: 0, _redirects: [], _requestBodyLength: 0, _requestBodyBuffers: [], _onNativeResponse: [Function (anonymous)], _currentRequest: ClientRequest { _events: [Object: null prototype], _eventsCount: 7, _maxListeners: undefined, outputData: [], outputSize: 0, writable: true, destroyed: false, _last: true, chunkedEncoding: false, shouldKeepAlive: false, maxRequestsOnConnectionReached: false, _defaultKeepAlive: true, useChunkedEncodingByDefault: false, sendDate: false, _removedConnection: false, _removedContLen: false, _removedTE: false, strictContentLength: false, _contentLength: 0, _hasBody: true, _trailer: '', finished: true, _headerSent: true, _closed: false, socket: [Socket], _header: 'GET /api/test HTTP/1.1\r\n' + 'Accept: application/json, text/plain, */*\r\n' + 'User-Agent: axios/1.7.2\r\n' + 'Accept-Encoding: gzip, compress, deflate, br\r\n' + 'Host: localhost:8080\r\n' + 'Connection: close\r\n' + '\r\n', _keepAliveTimeout: 0, _onPendingData: [Function: nop], agent: [Agent], socketPath: undefined, method: 'GET', maxHeaderSize: undefined, insecureHTTPParser: undefined, joinDuplicateHeaders: undefined, path: '/api/test', _ended: false, res: null, aborted: false, timeoutCb: null, upgradeOrConnect: false, parser: null, maxHeadersCount: null, reusedSocket: false, host: 'localhost', protocol: 'http:', _redirectable: [Circular *1], [Symbol(kCapture)]: false, [Symbol(kBytesWritten)]: 0, [Symbol(kNeedDrain)]: false, [Symbol(corked)]: 0, [Symbol(kOutHeaders)]: [Object: null prototype], [Symbol(errored)]: null, [Symbol(kHighWaterMark)]: 16384, [Symbol(kRejectNonStandardBodyWrites)]: false, [Symbol(kUniqueHeaders)]: null }, _currentUrl: 'http://localhost:8080/api/test', [Symbol(kCapture)]: false }, cause: Error: connect ECONNREFUSED ::1:8080 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1549:16) at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:128:17) { errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '::1', port: 8080 } }

解決策

宛先をlocalhostではなく127.0.0.1にしたら解決しました

axios.get('http://localhost:8080/api/test');

ではなく

axios.get('http://127.0.0.1:8080/api/test');

にしたら通信成功

原因

IPv6で通信しようとしていてできなかった様子
127.0.0.1と明示的にIPv4を使うことで通信できたようです。

以下chatGPTの回答抜粋

エラーメッセージから、::1というIPv6アドレスが使用されていることがわかります。IPv4アドレス127.0.0.1を使用するように試みます。

エラーの中身を見ていると確かにアドレスが::1となっています。

  address: '::1',
  syscall: 'connect',
  code: 'ECONNREFUSED',
  errno: -111,

なぜIPv6が優先して使われたのか、またなぜIPv6だと通信できないのかなどについては深堀りできていません。
通信できたので取り合えずOKとします。

補足ですが、dockerコンテナ内の/etc/hostの中身は以下でした

# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateHosts = false
127.0.0.1       localhost
127.0.1.1       toko202406.     toko202406

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

これを見るとlocalhost→127.0.0.1のIPv4で名前解決されると思うのですが原因不明です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0