PHP: Guzzle 6 - cURL error 7 Connection Refused
Asked Answered
D

4

7

I've searched and searched, and read the documentation at http://docs.guzzlephp.org/en/stable/request-options.html and confirmed the error at https://curl.haxx.se/libcurl/c/libcurl-errors.html and for the life of me, I cannot figure out what's going on. I have the URLs for both app-one and app-two in my /etc/hosts file, and I know they're correct as I can access them in my browser and with cURL via terminal just fine.

My setup:

Docker containers configured as:

App 1 = php-fpm - responding app
App 2 = php-fpm - requesting app, using Guzzle 6.3.2
Nginx Reverse Proxy

nginx configurations:

App 1:

upstream php-app-one {
    server php-app-one:9000;
}

server {
    listen 80;
    listen [::]:80;
    server_name app-one.local;
    return 301 https://$server_name$request_uri;
}

server {
    # SSL configuration
    listen 443 ssl;
    listen [::]:443 ssl;

    ssl on;
    ssl_certificate /etc/nginx/certs/app-one.crt;
    ssl_certificate_key /etc/nginx/certs/app-one.key;
    ssl_dhparam /etc/nginx/certs/dhparam.pem;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_ecdh_curve secp384r1;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    server_name app-one.local;

    root /var/www/app-one;
    index index.php index.html;

    gzip_types text/plain text/css application/json application/x-javascript
               text/xml application/xml application/xml+rss text/javascript;

    # Add headers to serve security related headers
    #
    # Disable preloading HSTS for now.  You can use the commented out header line that includes
    # the "preload" directive if you understand the implications.
    # add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Robots-Tag none;
    add_header Pragma "no-cache";
    add_header Cache-Control "no-cache";
    add_header X-uri "$uri";

    location ~* \.(eot|otf|ttf|woff|woff2)$ {
            add_header Access-Control-Allow-Origin *;
    }

    location / {
            proxy_read_timeout    90;
            proxy_connect_timeout 90;
            proxy_redirect        off;

            proxy_set_header      X-Real-IP $remote_addr;
            proxy_set_header      X-Scheme $scheme;
            proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header      X-Forwarded-Proto $scheme;
            proxy_set_header      X-Forwarded-Host $server_name;
            proxy_set_header      Host $host;
            proxy_set_header      X-Forwarded-Port 443;
            proxy_set_header      Authorization $http_authorization;
            proxy_pass_header     Authorization;

            try_files $uri $uri/ /index.php?$args;
    }

    # Pass all .php files onto a php-fpm/php-fcgi server.
    location ~ [^/]\.php(/|$) {
            add_header X-debug-message "A php file was used" always;
            # regex to split $uri to $fastcgi_script_name and $fastcgi_path
            fastcgi_split_path_info ^(.+?\.php)(/.*)$;
            # This is a robust solution for path info security issue and 
            # works with "cgi.fix_pathinfo = 1" in /etc/php.ini (default)
            # if (!-f $document_root$fastcgi_script_name) {
            #         return 404;
            # }
            # Check that the PHP script exists before passing it
            # try_files $fastcgi_script_name =404;
            # Bypass the fact that try_files resets $fastcgi_path_info
            # see: http://trac.nginx.org/nginx/ticket/321
            set $path_info $fastcgi_path_info;
            fastcgi_param PATH_INFO $path_info;
            fastcgi_intercept_errors on;
            fastcgi_pass php-app-one;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
            add_header X-debug-message "A static file was served" always;
            expires max;
            # log_not_found off;
    }

    location ~ /\. {
            deny all;
    }
}

App 2:

upstream php-app-two {
    server php-app-two:9000;
}

server {
    listen 80;
    listen [::]:80;
    server_name app-two.local;
    return 301 https://$server_name$request_uri;
}

server {
    # SSL configuration
    listen 443 ssl;
    listen [::]:443 ssl;

    ssl_certificate /etc/nginx/certs/app-two.crt;
    ssl_certificate_key /etc/nginx/certs/app-two.key;
    ssl_dhparam /etc/nginx/certs/dhparam.pem;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_ecdh_curve secp384r1;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    server_name app-two.local;

    root /var/www/app-two;
    index index.php index.html;

    gzip_types text/plain text/css application/json application/x-javascript
               text/xml application/xml application/xml+rss text/javascript;


    # Add headers to serve security related headers
    #
    # Disable preloading HSTS for now.  You can use the commented out header line that includes
    # the "preload" directive if you understand the implications.
    # add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Robots-Tag none;
    add_header Pragma "no-cache";
    add_header Cache-Control "no-cache";
    add_header X-uri "$uri";

    location ~* \.(eot|otf|ttf|woff|woff2)$ {
            add_header Access-Control-Allow-Origin *;
    }

    location / {
            proxy_read_timeout    90;
            proxy_connect_timeout 90;
            proxy_redirect        off;

            proxy_set_header      X-Real-IP $remote_addr;
            proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header      X-Forwarded-Host $server_name;
            proxy_set_header      Host $host;
            proxy_set_header      X-Forwarded-Proto $scheme;
            proxy_set_header      X-Forwarded-Port 443;
            proxy_set_header      Authorization $http_authorization;
            proxy_pass_header     Authorization;

            try_files $uri $uri/ /index.php;
    }

    # Pass all .php files onto a php-fpm/php-fcgi server.
    location ~ [^/]\.php(/|$) {
            add_header X-debug-message "A php file was used" always;
            # add_header Location "$uri" always;
            # regex to split $uri to $fastcgi_script_name and $fastcgi_path
            fastcgi_split_path_info ^(.+?\.php)(/.*)$;
            # This is a robust solution for path info security issue and 
            # works with "cgi.fix_pathinfo = 1" in /etc/php.ini (default)
            if (!-f $document_root$fastcgi_script_name) {
                    return 404;
            }
            # Check that the PHP script exists before passing it
            try_files $fastcgi_script_name =404;
            # Bypass the fact that try_files resets $fastcgi_path_info
            # see: http://trac.nginx.org/nginx/ticket/321
            set $path_info $fastcgi_path_info;
            fastcgi_param PATH_INFO $path_info;
            fastcgi_intercept_errors on;
            fastcgi_pass php-app-two;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
            expires max;
            log_not_found off;
    }
}

Nginx Reverse Proxy:

worker_processes 1;

daemon off;

events {
    worker_connections 1024;
}

error_log   /var/log/nginx/error.log warn;
pid         /var/run/nginx.pid;

http {
    default_type application/octet-stream;
    include /etc/nginx/conf/mime.types;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;

    keepalive_timeout 65;

    gzip on;
    gzip_disable "msie6";

    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    application/x-font-ttf ttc ttf;
    application/x-font-otf otf;
    application/font-woff woff;
    application/font-woff2 woff2;
    application/vnd.ms-fontobject eot;

    include /etc/nginx/conf.d/*.conf;
}

docker-compose.yml:

version: '3.3'
services:
  # configured to act as a proxy for wp and member portal
  nginx:
    image: evild/alpine-nginx:1.9.15-openssl
    container_name: nginx
    # volumes offer persistent storage
    volumes:
      - ./app_one:/var/www/app_one/:ro
      - ./app_two:/var/www/app_two/:ro
      - ./nginx/conf/nginx.conf:/etc/nginx/conf/default.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./certs:/etc/nginx/certs
    # ports to bind to
    ports:
      - 80:80
      - 443:443
    # allows service to be accessible by other docker containers
    expose:
      - "80"
      - "443"
    depends_on:
      - php-app_one
      - php-app_two
    environment: 
      TZ: "America/Los_Angeles"


  # app-two php container
  php-app_two:
    environment: 
      TZ: "America/Los_Angeles"
    image: joebubna/php
    container_name: app_two_php
    restart: always
    volumes:
      - ./app_two:/var/www/app_two
    ports:
      - 9000:9000

   php-app_one:
     environment: 
       TZ: "America/Los_Angeles"
     image: joebubna/php
     container_name: app_one_php
     restart: always
     volumes:
       - ./app-one:/var/www/app-one
     ports:
       - 9001:9000

  db:
    image: mysql:5.6
    container_name: app_two_mysql
    volumes:
      - db-data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/conf.d/ZZ-app-one.cnf:ro
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_DATABASE: cora
      TZ: "America/Los_Angeles"
    ports:
      - 3306:3306
    expose:
      - "3306"

    volumes:
      db-data:

App 1 and App 2 have SSL enabled with a self signed certificates that are imported on creation by docker-compose.

App 1 has several API endpoints App 2 needs to access. When I try to access via Guzzle, I receive:

Fatal error: Uncaught GuzzleHttp\Exception\ConnectException: cURL error 7: Failed to connect to app-one.local port 443: Connection refused (see http://curl.haxx.se/libcurl/c/libcurl-errors.html) in /var/www/app/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php on line 185

GuzzleHttp\Exception\ConnectException: cURL error 7: Failed to connect to app-one.local port 443: Connection refused (see http://curl.haxx.se/libcurl/c/libcurl-errors.html) in /var/www/app/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php on line 185

Call Stack:
0.0026     366656   1. {main}() /var/www/app/index.php:0
0.2229    3355944   2. Cora\Route->routeProcess() /var/www/app/index.php:45
0.2230    3357208   3. Cora\Route->routeFind() /var/www/app/vendor/cora/cora-framework/system/classes/Route.php:89
0.2240    3357912   4. Cora\Route->routeFind() /var/www/app/vendor/cora/cora-framework/system/classes/Route.php:474
0.2245    3358576   5. Cora\Route->getController() /var/www/app/vendor/cora/cora-framework/system/classes/Route.php:441
0.2364    3477872   6. Controllers\Api\Dashboard->__construct() /var/www/app/vendor/cora/cora-framework/system/classes/Route.php:501
0.2984    4086336   7. GuzzleHttp\Client->get() /var/www/app/controllers/api/controller.Dashboard.php:36
0.2984    4086712   8. GuzzleHttp\Client->__call() /var/www/app/controllers/api/controller.Dashboard.php:36
0.2984    4086712   9. GuzzleHttp\Client->request() /var/www/app/vendor/guzzlehttp/guzzle/src/Client.php:89
0.3521    4321000  10. GuzzleHttp\Promise\RejectedPromise->wait() /var/www/app/vendor/guzzlehttp/guzzle/src/Client.php:131

This is how I'm currently implementing the client (including some of the code I've added in my attempts to remedy this):

<?php
namespace Controllers\Api;

use \GuzzleHttp\Client;
// use \GuzzleHttp\Psr7\Uri;

define('URL', 'https://app-one.local/api/');

class Dashboard extends ApiController 
{
    private $http;

    public function __construct($container)
    {
        // We're using guzzle for our requests to help keep opportunity
        // for cURL errors to a minimum
        $this->http = new Client([
            'base_uri'          => URL,
            'timeout'           => 30.0,
            'allow_redirects'   => true,
            'verify'            => false,
            'curl'              => [
                 CURLOPT_VERIFYPEER => false
            ],
            'headers'           => [
                'User-Agent'        => 'curl/7.38.0',
            ],
        ]);

        $response = $this->http->get('member/sales/hasalestest');
        var_dump($response);
        exit;
    }
}

As I mentioned, I can access this endpoint via browser just fine, and can access it directly with cURL in the terminal so long I use the -k flag for "insecure". I'm not sure what else I can do, as Guzzle's documentation isn't very clear on the syntax differences between 5 and 6. Then the Drupal and Laravel crowds tend to have unrelated issues.

This SO post seemed similar (minus the hard-coded port number and Guzzle v.5) but doesn't mention anything I haven't tried: PHP Guzzle 5: Cannot handle URL with PORT number in it .

This question is also of interest, but based on other apps that interact with App 1, it does allow other apps to consume certain API endpoints: cURL error 7: Failed to connect to maps.googleapis.com port 443

All I can think of at this point is maybe it's an nginx configuration issue? A push in the right direction is all I need to get moving forward and get the rest of the endpoints I need to consume, being consumed.

Thanks for any guidance!

Demoss answered 18/4, 2018 at 1:33 Comment(2)
Are the two app containers able to communicate with each other?Raynold
@Raynold I believe so. I'll update the question with the nginx configurations.Demoss
D
3

This turned out to be a relatively simple fix. The problem was the two fpm containers weren't aware of each other, and by referring to app-one.local in app-two's request, app-two was basically sending the request into the void. The fix for this was as follows:

version: '3.3'
services:
  nginx:
    image: evild/alpine-nginx:1.9.15-openssl
    container_name: nginx
    volumes:
      - ./app-one:/var/www/app-one/:ro
      - ./app-two:/var/www/app-two/:ro
      - ./nginx/conf/nginx.conf:/etc/nginx/conf/default.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./certs:/etc/nginx/certs
    ports:
      - 80:80
      - 443:443
    expose:
      - "80"
      - "443"
    depends_on:
      - app-one
      - app-two
    environment: 
      TZ: "America/Los_Angeles"
    # This is the fix
    networks:
      default:
        aliases:
          - app-one.local
          - app-two.local

  app-one:
    environment: 
      TZ: "America/Los_Angeles"
    image: joebubna/php
    container_name: app-one
    restart: always
    volumes:
      - ./app-one:/var/www/app-one
    ports:
      - 9000:9000
    # This is the fix
    networks:
      - default

  app-two:
    environment: 
      TZ: "America/Los_Angeles"
    image: joebubna/php
    container_name: app-two
    restart: always
    volumes:
      - ./app-two:/var/www/app-two
    ports:
      - 9001:9000
    # This is the fix
    networks:
      - default

  db:
    image: mysql:5.6
    container_name: mysql
    volumes:
      - db-data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/conf.d/ZZ-mysql.cnf:ro
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_DATABASE: cora
      TZ: "America/Los_Angeles"
    ports:
      - 3306:3306
    expose:
      - "3306"
    # This is the fix
    networks:
      - default

volumes:
  db-data:
# This is the fix
networks:
  default:
    driver: bridge

What I ended up doing is creating an overlay network, and making the nginx container aware of each of the fpm's domain name. This allows the two containers to now send requests back and forth between each other via FQDN as opposed to IP or container ID/name. A simple thing to overlook in hindsight.

Demoss answered 19/4, 2018 at 23:52 Comment(0)
M
3

The issue is that your hosts file on your local machine will not impact how the docker instances map an IP to a host.

Try accessing the endpoints via the container name...

Momentous answered 18/4, 2018 at 2:31 Comment(4)
I'm not sure I understand what you're saying here. Is there a method of accessing the endpoints by container name via php? I thought the reverse proxying nginx does would take care of mapping the URI to the appropriate container.Demoss
Hi. Yeah, providing the containers are connected to the default bridge which they should be. Maybe start by checking this by running 'docker network inspect bridge' - you should see your containers in this bridge. You will see their IP addresses. Try with IP also (to troubleshoot).Momentous
So, after much experimentation, and combing through the docs, plus getting my company's devops guy involved, we moved past this error. Now, I have another error related to the codebase. What ended up needing to be done was creating an overlay network, and create aliases for each app. This enabled them to start talking to each other. Thank you for your assistance @cherrysoft!Demoss
Glad you got it sorted and +1 for adding solution.Momentous
D
3

This turned out to be a relatively simple fix. The problem was the two fpm containers weren't aware of each other, and by referring to app-one.local in app-two's request, app-two was basically sending the request into the void. The fix for this was as follows:

version: '3.3'
services:
  nginx:
    image: evild/alpine-nginx:1.9.15-openssl
    container_name: nginx
    volumes:
      - ./app-one:/var/www/app-one/:ro
      - ./app-two:/var/www/app-two/:ro
      - ./nginx/conf/nginx.conf:/etc/nginx/conf/default.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./certs:/etc/nginx/certs
    ports:
      - 80:80
      - 443:443
    expose:
      - "80"
      - "443"
    depends_on:
      - app-one
      - app-two
    environment: 
      TZ: "America/Los_Angeles"
    # This is the fix
    networks:
      default:
        aliases:
          - app-one.local
          - app-two.local

  app-one:
    environment: 
      TZ: "America/Los_Angeles"
    image: joebubna/php
    container_name: app-one
    restart: always
    volumes:
      - ./app-one:/var/www/app-one
    ports:
      - 9000:9000
    # This is the fix
    networks:
      - default

  app-two:
    environment: 
      TZ: "America/Los_Angeles"
    image: joebubna/php
    container_name: app-two
    restart: always
    volumes:
      - ./app-two:/var/www/app-two
    ports:
      - 9001:9000
    # This is the fix
    networks:
      - default

  db:
    image: mysql:5.6
    container_name: mysql
    volumes:
      - db-data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/conf.d/ZZ-mysql.cnf:ro
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_DATABASE: cora
      TZ: "America/Los_Angeles"
    ports:
      - 3306:3306
    expose:
      - "3306"
    # This is the fix
    networks:
      - default

volumes:
  db-data:
# This is the fix
networks:
  default:
    driver: bridge

What I ended up doing is creating an overlay network, and making the nginx container aware of each of the fpm's domain name. This allows the two containers to now send requests back and forth between each other via FQDN as opposed to IP or container ID/name. A simple thing to overlook in hindsight.

Demoss answered 19/4, 2018 at 23:52 Comment(0)
N
1

just find your IP in windows or Linux. for example my IP is 192.168.10.xx and change localhost to it. http://192.168.10.xx:8080

Neff answered 4/5, 2023 at 15:29 Comment(0)
K
0

In My case URL was not valid i was missing the "https://" in start of URL. when add it was fine

Kun answered 15/10, 2018 at 20:32 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.