26

So when I'm locally testing things such as Ajax in apps I'm writing, I often like to add a delay in server side scripts using a sleep statement. It helps simulate slow connections etc.

Is there a way to specify a similar delay behaviour directly in Nginx config that would work for the flat HTML files it's serving?

I'm aware you can do a similar delay simulation at the network level (see here) but it seems pretty messy and has never worked very well for me.

2

8 Answers 8

31

You should try an echo module.

1
  • Brilliant. That's exactly what I needed :-) Knew there must be a way! Sep 8, 2013 at 22:09
11

Giving a more detailed explanation of how you might use the echo module:

If you're starting from a basic config, that loads static files and PHP files, with something like this:

location ~ \.php$ {
    include fastcgi.conf;
    fastcgi_pass php;
}

That can then be converted to something like this to add a delay to both static and PHP requests:

# Static files
location / {
    echo_sleep 5;
    echo_exec @default;
}
location @default {}

# PHP files
location ~ \.php$ {
    echo_sleep 5;
    echo_exec @php;
}
location @php {
    include fastcgi.conf;
    fastcgi_pass php;
}

This can obviously be modified for anything you want. Basically, move each location block into a named @location. Then use echo_sleep and echo_exec in the original location block.

5

I would like to add to astlock's answer that if you want to reply with a plain return then note that there's a caveat: you have to use echo, not a standard return directive, after echo_sleep to respond with a delay, like this:

location = /slow-reply {
  echo_sleep 5.0;
  #return 200 'this response would NOT be delayed!';      
  echo 'this text will come in response body with HTTP 200 after 5 seconds';
}

(Tested on openresty/1.7.10.2)

2

Hackish way of doing this without external modules:

You build a plain HTTP server serving the asset with bash, it can make use of pv -L to throttle down the rate at which it is read from disk thus delaying the response:

#!/bin/bash

bytes_second=$1
port=$2
mime_type=$3
asset_path=$4

function asset_response() {
    asset_size=$(du -b $asset_path)

    echo "HTTP/1.1 200 OK
Server: fake_server/0.0.0
Content-Type: text/html
Content-Length: $asset_size

$(pv -L $bytes_second $asset_path)"
}

while [ 1 ]; do
    asset_response | nc -l $port
done

(./fake_server.sh)

./fake_server.sh 500 8080 text/html index.html

Then you can just set Nginx up to proxy requests to that bash HTTP server:

    server {
           location /fake_proxy {
                    proxy_pass http://localhost:8080;
           }
2
  • Tried this but the server never responds until I CTLR+C the script Aug 29, 2023 at 23:25
  • @SteveRobbins just tested it again, the terminal where the server starts "hangs" until you hit ctrl+c as the program takes control of it, you can run it in background mode. Yet, if you hit its endpoint (curl localhost:8080) you'll see output on that terminal and curl with get its response. Aug 30, 2023 at 9:38
0

The following python script worked well for me and IMHO worth sharing.

#!/usr/bin/env python

# Includes
import getopt
import sys
import os.path
import subprocess
from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler
import socketserver
import time

########  Predefined variables #########
helpstring = """Usage: {scriptname} args...
    Where args are:
        -h, --help
            Show help
        -p PORTNUMBER
            Port number to run on
        -d delay-in-seconds
            How long to wait before responding
"""

helpstring = helpstring.format(scriptname=sys.argv[0])

def beSlow(seconds):
    time.sleep(float(seconds))


########  Functions and classes #########
class SlowserverRequestHandler(BaseHTTPRequestHandler):
    def do_GET(s):
        if s.path == "/slow":
            # Check status
            # Assume fail
            code = 200
            status = ""

            # Be slow for a while
            beSlow(seconds)

            s.send_response(200)
            s.send_header("Content-type", "text/html")
            s.end_headers()
            s.wfile.write(b"I'm a slow response LOL\n")

        else:
            s.send_response(200)
            s.send_header("Content-type", "text/html")
            s.end_headers()
            s.wfile.write(b"slowserver - reporting for duty. Slowly...\n")


# Parse args
try:
    options, remainder = getopt.getopt(sys.argv[1:], "hp:d:", ['help'])
except:
    print("Invalid args. Use -h or --help for help.")
    raise
    sys.exit(1)

HTTPPORT = 8000
for opt, arg in options:                                                  
    if opt in ('-h', '--help'):                                           
        print(helpstring)                                                 
        sys.exit(0)                                                       
    elif opt in ('-p'):                                                   
        HTTPPORT = int(arg)                                               
    elif opt in ('-d'):                                                
        seconds = arg                                                  
                                                                       
# Start HTTP service                                                   
server_class=HTTPServer                                                
handler_class=SlowserverRequestHandler               
server_address = ('', HTTPPORT)                      
httpd = server_class(server_address, handler_class)
try:                                               
    httpd.serve_forever()                          
except KeyboardInterrupt:                          
    pass                                           
httpd.server_close()

Source (gist.github.com)

0

I had to simulate delays (to generate timeouts every now and then) and, although the result is not as constant as other answers, the following is a quick implementation.

In practice one uses limit_req or request limiting. For example with an allowance for 1 requests per minute, the first request will get through but then the rest will be either rejected or delayed. If one configures a proper burst one can achieve substaintial delays (that indeed easily result in connection timeouts). Setting the burst is important otherwise there is only rejection and no delay.

    limit_req_zone $binary_remote_addr zone=adddelay:2m rate=1r/m;

    server {
      listen 80;

      include /etc/nginx/conf.d/restricted_access.cnf;
      root    /usr/share/nginx/html/;

      location / {
        limit_req zone=adddelay burst=100; #up to 100 mins delay
        proxy_pass http://endpoint;
      }
    }

More info here: Limiting the Request Rate section.

-1

you can set a backend server to receive the request, then delay to response in the server. Nginx proxy the request to the backend server.

-4

There is no way (IHMO) to delay the response from nginx. You have to rework your application not your webserver to enable such feature.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .