Asynchronous HTTP requests
last modified January 10, 2023
The asynchronous HTTP requests tutorial shows how to create async HTTP requests in Go, C#, F#, Groovy, Python, Perl, Java, JavaScript, and PHP.
Asynchronous requests do not block the client and allow us to generate HTTP requests more efficiently.
Rather than generating requests one by one, waiting for the current request to finish before executing next one, we execute all requests quickly and then wait for all of them to finish.
Go async requests
Go has goroutines for making asynchronous requests. A goroutine is a lightweight thread managed by the Go runtime.
package main import ( "fmt" "io/ioutil" "log" "net/http" "regexp" "sync" ) func main() { urls := []string{ "http://webcode.mse", "https://example.com", "http://httpbin.org", "https://www.perl.org", "https://www.php.net", "https://www.python.org", "https://code.visualstudio.com", "https://clojure.org", } var wg sync.WaitGroup for _, u := range urls { wg.Add(1) go func(url string) { defer wg.Done() content := doReq(url) title := getTitle(content) fmt.Println(title) }(u) } wg.Wait() } func doReq(url string) (content string) { resp, err := http.Get(url) if err != nil { log.Println(err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Println(err) return } return string(body) } func getTitle(content string) (title string) { re := regexp.MustCompile("<title>(.*)</title>") parts := re.FindStringSubmatch(content) if len(parts) > 0 { return parts[1] } else { return "no title" } }
We make multiple asynchronous HTTP requests. We get the contents of the
title
tag of each of the web pages.
var wg sync.WaitGroup
WaitGroups
are used to manage goroutines. It waits for a collection
of goroutines to finish.
go func(url string) { defer wg.Done() content := doReq(url) title := getTitle(content) fmt.Println(title) }(u)
A goroutine is created with the go
keyword.
$ go run async_req.go The Perl Programming Language - www.perl.org Welcome to Python.org Visual Studio Code - Code Editing. Redefined PHP: Hypertext Preprocessor Example Domain httpbin.org Clojure My html page
C# async requests
In C#, we use the HttpClient
to generate asynchronous requests.
using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using System.Text.RegularExpressions; var urls = new string[] { "http://webcode.me", "http://example.com", "http://httpbin.org", "https://ifconfig.me", "http://termbin.com", "https://github.com" }; var rx = new Regex(@"<title>\s*(.+?)\s*</title>", RegexOptions.Compiled); using var client = new HttpClient(); var tasks = new List<Task<string>>(); foreach (var url in urls) { tasks.Add(client.GetStringAsync(url)); } Task.WaitAll(tasks.ToArray()); var data = new List<string>(); foreach (var task in tasks) { data.Add(await task); } foreach (var content in data) { var matches = rx.Matches(content); foreach (var match in matches) { Console.WriteLine(match); } }
We download the given web pages asynchronously and print their HTML title tags.
tasks.Add(client.GetStringAsync(url));
The GetStringAsync
sends a GET request to the specified url and
returns the response body as a string in an asynchronous operation. It returns a
new task; in C# a task represents an asynchronous operation.
Task.WaitAll(tasks.ToArray());
The Task.WaitAll
waits for all of the provided tasks to complete
execution.
data.Add(await task);
The await
keywords unwraps the result value.
$ dotnet run <title>My html page</title> <title>Example Domain</title> <title>httpbin.org</title> <title>termbin.com - terminal pastebin</title> <title>GitHub: Where the world builds software · GitHub</title>
F# async requests
The following example uses HttpClient
and task expressions to
fetch website titles asynchronously.
open System.Net.Http open System.Text.RegularExpressions open System.Threading.Tasks let fetchTitleAsync (url: string) = task { use client = new HttpClient() let! html = client.GetStringAsync(url) let pattern = "<title>\s*(.+?)\s*</title>" let m = Regex.Match(html, pattern) return m.Value } let sites = [| "http://webcode.me" "http://example.com" "https://bing.com" "http://httpbin.org" "https://ifconfig.me" "http://termbin.com" "https://github.com" |] let titles = sites |> Array.map fetchTitleAsync |> Task.WhenAll |> Async.AwaitTask |> Async.RunSynchronously titles |> Array.iter (fun title -> printfn $"%s{title}")
The example asynchronously retrieves the titles of the given urls.
Another solution uses the WebRequest
to generate a request. Its
GetResponseStream
returns a response to a request as an
asynchronous operation.
open System.Net open System open System.Text.RegularExpressions let fetchTitleAsync url = async { let req = WebRequest.Create(Uri(url)) use! resp = req.AsyncGetResponse() use stream = resp.GetResponseStream() use reader = new IO.StreamReader(stream) let html = reader.ReadToEnd() let pattern = "<title>\s*(.+?)\s*</title>" let m = Regex.Match(html, pattern) return m.Value } let sites = [ "http://webcode.me" "http://example.com" "https://bing.com" "http://httpbin.org" "https://ifconfig.me" "http://termbin.com" "https://github.com" ] let titles = sites |> List.map fetchTitleAsync |> Async.Parallel |> Async.RunSynchronously titles |> Array.iter (fun title -> printfn $"%s{title}")
The example asynchronously retrieves the titles of the given urls.
Groovy async requests
In Groovy, we use ExecutorService
and HttpClient
.
import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse int nThreads = 30 def executor = Executors.newFixedThreadPool(nThreads) def urls = [ "https://crunchify.com", "https://yahoo.com", "https://www.ebay.com", "https://google.com", "https://www.example.co", "https://paypal.com", "http://bing.com/", "https://techcrunch.com/", "http://mashable.com/", "https://pro.crunchify.com/", "https://wordpress.com/", "https://wordpress.org/", "https://example.com/", "https://sjsu.edu/", "https://ask.crunchify.com/", "https://test.com.au/", "https://www.wikipedia.org/", "https://en.wikipedia.org" ] for (String url in urls ) { executor.execute(() -> { worker(url) // try { // worker(url) // } catch (Exception e) { // e.printStackTrace() // } }) } executor.shutdown() executor.awaitTermination(30, TimeUnit.SECONDS) println("finished") def worker(url) { def client = HttpClient.newHttpClient() def request = HttpRequest.newBuilder() .uri(URI.create(url)) .build() HttpResponse<Void> res = client.send(request, HttpResponse.BodyHandlers.discarding()) println "${url}: ${res.statusCode()}" }
The example makes multiple asynchronous requests to URLs and prints their status codes.
$ groovy mul_async_req.gvy http://mashable.com/: 301 http://bing.com/: 301 https://paypal.com: 302 https://en.wikipedia.org: 301 https://paypal.com: 302 https://en.wikipedia.org: 301 https://en.wikipedia.org: 301 https://google.com: 301 https://example.com/: 200 https://example.com/: 200 https://yahoo.com: 301 https://test.com.au/: 301 https://wordpress.com/: 200 https://techcrunch.com/: 200 https://www.ebay.com: 200 https://ask.crunchify.com/: 200 https://pro.crunchify.com/: 200 https://sjsu.edu/: 200 finished
Python async requests
In Python, we use the httpx
and asyncio
modules.
#!/usr/bin/python import httpx import asyncio async def get_async(url): async with httpx.AsyncClient() as client: return await client.get(url) urls = ['http://webcode.me', 'https://httpbin.org/get', 'https://google.com', 'https://stackoverflow.com', 'https://github.com'] async def launch(): resps = await asyncio.gather(*map(get_async, urls)) data = [resp.status_code for resp in resps] for status_code in data: print(status_code) asyncio.run(launch())
The example makes asynchronous requests in Python. It prints the status code of all the provided urls.
./async_req.py 200 200 200 200 200
Perl async requests
In Perl, we use the LWP
module to generate requests and the
Parallel::ForkManager
module to make them asynchronous.
$ cpanm Parallel::ForkManager LWP
We install the modules with cpanm
.
http://webcode.me https://example.com http://httpbin.org https://google.com https://www.perl.org https://fsharp.org https://clojure.org https://www.rust-lang.org https://golang.org https://www.python.org https://code.visualstudio.com https://ifconfig.me http://termbin.com https://github.com https://stackoverflow.com https://www.php.net/
The urls.txt
contains a list of websites.
#!/usr/bin/perl use warnings; use 5.30.0; use Path::Tiny; use LWP::UserAgent; use Parallel::ForkManager; my @urls = split "\n", path('urls.txt')->slurp_utf8; my $pm = Parallel::ForkManager->new(4); my $ua = LWP::UserAgent->new; $ua->agent('Perl script'); say "downloading ", scalar @urls, " files"; my $dir = 'files/'; mkdir $dir if not -d $dir; foreach my $link (@urls) { my $name = $1 if $link =~ m%https?://(.+)\.\w+%; my $file_name = "$dir/$name" . '.txt'; $pm->start and next; my $resp = $ua->get($link); if ($resp->is_success) { path($file_name)->spew_utf8($resp->decoded_content); } else { warn $resp->status_line } $pm->finish; } $pm->wait_all_children;
The example reads the urls.txt
file and gets the links. It
generates async requests to the given urls. The contents of the web pages
are written to files.
$ ./async_req.pl downloading 15 files $ ls -1 files/ clojure.txt code.visualstudio.txt example.txt fsharp.txt github.txt golang.txt google.txt httpbin.txt ifconfig.txt stackoverflow.txt termbin.txt webcode.txt www.perl.txt www.python.txt www.rust-lang.txt
JS async requests
For JavaScript, we have chosen the axios module.
$ npm i axios
We install the axios module.
const axios = require('axios'); async function makeRequests(urls) { const fetchUrl = (url) => axios.get(url); const promises = urls.map(fetchUrl); let responses = await Promise.all(promises); responses.forEach(resp => { let msg = `${resp.config.url} -> ${resp.headers.server}: ${resp.status}`; console.log(msg); }); } let urls = [ 'http://webcode.me', 'https://example.com', 'http://httpbin.org', 'https://clojure.org', 'https://fsharp.org', 'https://symfony.com', 'https://www.perl.org', 'https://www.php.net', 'https://www.python.org', 'https://code.visualstudio.com', 'https://github.com' ]; makeRequests(urls);
The example generates async requests to the given list of urls. It prints the web site's url, server name, and status code.
const fetchUrl = (url) => axios.get(url);
The axios.get
makes an async request and returns a promise.
let responses = await Promise.all(promises);
We collect all promises with Promise.All
. The method resolves after
all of the given promises have either fulfilled or rejected.
$ node async_req.js http://webcode.me -> nginx/1.6.2: 200 https://example.com -> ECS (dcb/7F83): 200 http://httpbin.org -> gunicorn/19.9.0: 200 https://clojure.org -> AmazonS3: 200 https://fsharp.org -> GitHub.com: 200 https://symfony.com -> cloudflare: 200 https://www.perl.org -> Combust/Plack (Perl): 200 https://www.php.net -> myracloud: 200 https://www.python.org -> nginx: 200 https://code.visualstudio.com -> Microsoft-IIS/10.0: 200 https://github.com -> GitHub.com: 200
Java async requests
The CompletableFuture
a high-level API for asynchronous
programming in Java.
package com.zetcode; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; public class AsyncReqEx { public static void main(String[] args) { List<URI> uris = Stream.of( "https://www.google.com/", "https://clojure.org", "https://www.rust-lang.org", "https://golang.org", "https://www.python.org", "https://code.visualstudio.com", "https://ifconfig.me", "http://termbin.com", "https://www.github.com/" ).map(URI::create).collect(toList()); HttpClient httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .followRedirects(HttpClient.Redirect.ALWAYS) .build(); var futures = uris.stream() .map(uri -> verifyUri(httpClient, uri)) .toArray(CompletableFuture[]::new); CompletableFuture.allOf(futures).join(); } private static CompletableFuture<Void> verifyUri(HttpClient httpClient, URI uri) { HttpRequest request = HttpRequest.newBuilder() .timeout(Duration.ofSeconds(5)) .uri(uri) .build(); return httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()) .thenApply(HttpResponse::statusCode) .thenApply(statusCode -> statusCode == 200) .exceptionally(ex -> false) .thenAccept(valid -> { if (valid) { System.out.printf("[SUCCESS] Verified %s%n", uri); } else { System.out.printf("[FAILURE] Failed to verify%s%n", uri); } }); } }
In the example, we have a list of urls. We check the status of the given web
pages. The example uses HttpClient
for making a web request and
CompletableFuture
for asynchronous execution.
[SUCCESS] Verified http://termbin.com [SUCCESS] Verified https://clojure.org [SUCCESS] Verified https://www.google.com/ [SUCCESS] Verified https://ifconfig.me [SUCCESS] Verified https://www.python.org [SUCCESS] Verified https://code.visualstudio.com [SUCCESS] Verified https://golang.org [SUCCESS] Verified https://www.rust-lang.org [SUCCESS] Verified https://www.github.com/
PHP async requests
In PHP, we use the cURL library.
<?php $urls = [ "http://webcode.me", "https://example.com", "http://httpbin.org", "https://www.perl.org", "https://www.php.net", "https://www.python.org", "https://code.visualstudio.com", "https://ifconfig.me" ]; $options = [CURLOPT_HEADER => true, CURLOPT_NOBODY => true, CURLOPT_RETURNTRANSFER => true]; $mh = curl_multi_init(); $chs = []; foreach ($urls as $url) { $ch = curl_init($url); curl_setopt_array($ch, $options); curl_multi_add_handle($mh, $ch); $chs[] = $ch; } $running = false; do { curl_multi_exec($mh, $running); } while ($running); foreach ($chs as $h) { curl_multi_remove_handle($mh, $h); } curl_multi_close($mh); foreach ($chs as $h) { $status = curl_getinfo($h, CURLINFO_RESPONSE_CODE); echo $status . "\n"; } foreach ($chs as $h) { echo "----------------------\n"; echo curl_multi_getcontent($h); }
We print the status codes and headers of the requested web pages.
$ch = curl_init($url);
The curl_multi_init
function creates a new multi handle, which
allows the processing of multiple cURL handles asynchronously.
$ php async_req.php 200 200 200 200 200 200 200 200 ---------------------- HTTP/1.1 200 OK Server: nginx/1.6.2 Date: Thu, 22 Jul 2021 13:14:22 GMT Content-Type: text/html Content-Length: 348 Last-Modified: Sat, 20 Jul 2019 11:49:25 GMT Connection: keep-alive ETag: "5d32ffc5-15c" Accept-Ranges: bytes ---------------------- HTTP/2 200 content-encoding: gzip accept-ranges: bytes ...
In this tutorial we have generated asynchronous web requests in Go, C#, F#, Python, Perl, Java, JavaScript, and PHP.