HTTP Requests in PHP without cURL

For Clipper/Harbour minds

Drop-in replacement with file_get_contents() + stream_context, a low‑level socket walkthrough for learning, and side‑by‑side parallels to Harbour/FiveWin.

Production‑oriented Harbour parallels Low‑level PoC included
Overview

Three ways to call HTTP in PHP — with Harbour/FiveWin analogies

Pick the level of abstraction that matches your needs. The closer you go to the wire, the more you must implement yourself.

High‑Level (recommended)
PHP: curl_* (libcurl)
Full feature set: redirects, HTTP/2, TLS tuning, auth, proxies, retries. Same engine as the curl CLI.
Harbour/FiveWin
Use an HTTP client class that wraps WinInet/WinHTTP.
Robust
Mid‑Level (simple & fast)
PHP: file_get_contents + stream_context
Perfect for simple JSON POST/GET to http://127.0.0.1 microservices. Minimal code, solid defaults.
Harbour/FiveWin
System HTTP wrapper that covers the 80% use case.
Lean
Low‑Level (learning)
PHP: fsockopen + manual HTTP/1.1
You build headers, read bytes, parse CRLF, handle chunking/TLS. Great to understand the stack; not great for production.
Harbour/FiveWin
hb_socket* + HBSSL, everything by hand.
Educational
Production‑ready snippet

Drop‑in replacement: from cURL to stream_context

Swap the cURL block for this minimal version when you post JSON to a local microservice. It keeps your code tiny and readable.

Scenario: POST http://127.0.0.1:9090/booklock_status with a JSON payload.
PHP — without cURL
$ctx = stream_context_create([
  'http' => [
    'method'        => 'POST',
    'header'        => ['Content-Type: application/json; charset=utf-8', 'Connection: close'],
    'content'       => json_encode($payload, JSON_UNESCAPED_UNICODE),
    'timeout'       => 5,
    'ignore_errors' => true
  ]
]);
$resp = @file_get_contents('http://127.0.0.1:9090/booklock_status', false, $ctx);

if ($resp === false) {
  http_response_code(502);
  echo json_encode(['success'=>false,'message'=>'proxy_error','error'=>'MS unreachable']);
  exit;
}

$statusLine = $http_response_header[0] ?? 'HTTP/1.1 200';
$parts = explode(' ', $statusLine);
$status = (int)($parts[1] ?? 200);
http_response_code($status);
echo $resp;

Tip: ignore_errors => true lets you read 4xx/5xx bodies for richer error JSON.

What stream_context_create() actually does

method
Sets HTTP verb (POST)
header
Appends HTTP headers (e.g. JSON Content‑Type)
content
Raw request body (your JSON string)
timeout
Network timeout in seconds
ignore_errors
Don’t throw on 4xx/5xx — still capture the body

The real work happens in file_get_contents('http://…', false, $ctx): PHP’s built‑in HTTP wrapper opens the socket, builds the request, reads the response, and returns the body. The received headers go into $http_response_header.

Learning corner

Low‑level socket (HTTP/1.1) — why it’s not for production

Great to understand what happens on the wire; painful to maintain in real projects.

PHP — hand‑rolled request
$socket = @fsockopen('127.0.0.1', 9090, $errno, $errstr, 5);
$body   = json_encode($payload, JSON_UNESCAPED_UNICODE);

$req  = "POST /booklock_status HTTP/1.1\r\n";
$req .= "Host: 127.0.0.1:9090\r\n";
$req .= "Content-Type: application/json; charset=utf-8\r\n";
$req .= "Content-Length: ".strlen($body)."\r\n";
$req .= "Connection: close\r\n\r\n";
$req .= $body;

fwrite($socket, $req);
$response = stream_get_contents($socket);
fclose($socket);
// Then split headers/body, parse status line, handle chunking…
Caveats: No TLS, no gzip/chunking, no HTTP/2, no retry/redirect logic. You’ll end up rebuilding cURL.
Mental model

Harbour/FiveWin parallels

  • PHP cURL ≈ Harbour high‑level HTTP client (WinInet/WinHTTP wrapper). Minimal code, maximum robustness.
  • PHP stream_context ≈ Harbour “system HTTP wrapper” — covers the common 80% with tiny code.
  • PHP fsockopen ≈ Harbour hb_socket* + HBSSL — total control, total responsibility.
Rule of thumb: If you wouldn’t hand‑code hb_socket framing in Harbour for production, don’t in PHP either.
Under the hood

Where does the HTTP status code come from?

  • cURL: libcurl reads the first line (HTTP/1.1 200 OK) and stores the number; you read it via curl_getinfo(..., CURLINFO_RESPONSE_CODE).
  • stream wrapper: headers are exposed via $http_response_header. The first line contains the status; parse it and mirror it with http_response_code($status).
Tooling

Debug & CLI testing with curl

Even if your PHP uses file_get_contents, keep the curl CLI for smoke tests, CI checks, and documentation.

curl — CLI example
curl -v -X POST http://127.0.0.1:9090/booklock_status \
  -H "Content-Type: application/json" \
  -d '{"dbname":"BELEGUNG","buchung_id":"12345","user":"TEST","intent":"edit"}'
Guidance

Practical recommendations

Use stream_context for simple localhost JSON
Fast, tiny, perfect for microservice proxies.
Mirror the microservice status
Parse $http_response_header[0] and call http_response_code().
Timeouts matter
3–5 s is enough for internal calls.
Clear error mapping
Network fail → 502 Bad Gateway; business errors → pass through MS JSON.
Scale up to cURL when needed
HTTPS tuning, proxies, auth, retries → switch to curl_*.
Quick win

Migration checklist: cURL → stream_context

  • Set Content‑Type to application/json; charset=utf‑8
  • Enable ignore_errors to read 4xx/5xx bodies
  • Apply a sensible timeout (e.g. 5s)
  • Adopt the microservice status code from $http_response_header
  • Return the raw MS JSON body unchanged
Reference

Abbreviations legend

HTTP
Protocol for web requests and responses between clients and servers.
TCP
Connection‑oriented transport protocol used under HTTP.
TLS
Encryption layer that makes HTTP secure (HTTPS).
cURL
Library and CLI tool for HTTP(S) requests; PHP exposes it via curl_*.
DLL
Dynamic‑link library on Windows, loaded by processes at runtime.
CLI
Command‑line interface; here, the curl terminal tool.
JSON
Text format for structured data (key‑value pairs).
PoC
Proof of Concept; minimal demo to show feasibility.
CI/CD
Continuous Integration/Continuous Deployment automation pipelines.
Proxy
A mediator that forwards requests to an upstream service.

Max. 10 entries • Collected on first occurrence • Plain‑language explanations