Issue
I've searched and searched, and read the documentation at rel="noreferrer">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!
Solution
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.
Answered By - TomJ Answer Checked By - Willingham (WPSolving Volunteer)