@@ -17,14 +17,46 @@ host_arch() {
1717
1818wait_for_file () {
1919 file=$1
20- waits=200
20+ waits=${2 :- 200}
2121 while [ ! -s " $file " ] && [ " $waits " -gt 0 ]; do
2222 sleep 0.1
2323 waits=$(( waits - 1 ))
2424 done
2525 [ -s " $file " ]
2626}
2727
28+ ensure_image () {
29+ image=$1
30+ if ! docker image inspect " $image " > /dev/null 2>&1 ; then
31+ if ! docker pull " $image " > /dev/null 2>&1 ; then
32+ printf ' %s\n' " Failed to pull image: $image " >&2
33+ return 1
34+ fi
35+ fi
36+ return 0
37+ }
38+
39+ container_running () {
40+ docker inspect -f ' {{.State.Running}}' " $1 " 2> /dev/null | grep -q true
41+ }
42+
43+ wait_for_container_port () {
44+ container=$1
45+ port=$2
46+ waits=${3:- 200}
47+ while [ " $waits " -gt 0 ]; do
48+ if docker exec " $container " sh -c " if command -v ncat >/dev/null 2>&1; then ncat -z 127.0.0.1 $port ; elif command -v nc >/dev/null 2>&1; then nc -z 127.0.0.1 $port ; else exit 1; fi" > /dev/null 2>&1 ; then
49+ return 0
50+ fi
51+ if ! container_running " $container " ; then
52+ return 1
53+ fi
54+ sleep 0.2
55+ waits=$(( waits - 1 ))
56+ done
57+ return 1
58+ }
59+
2860arch=${ARCH:- }
2961if [ -z " $arch " ]; then
3062 arch=$( host_arch) || {
@@ -137,7 +169,10 @@ template=$(mktemp)
137169streaming_dir=" "
138170streaming_code_dir=" "
139171streaming_request=" "
172+ streaming_next_request=" "
140173streaming_request_clean=" "
174+ streaming_api_log=" "
175+ streaming_runtime_log=" "
141176streaming_network=" "
142177streaming_api_container=" "
143178streaming_runtime_container=" "
@@ -161,6 +196,12 @@ cleanup() {
161196 if [ -n " $streaming_request_clean " ]; then
162197 rm -f " $streaming_request_clean "
163198 fi
199+ if [ -n " $streaming_api_log " ]; then
200+ rm -f " $streaming_api_log "
201+ fi
202+ if [ -n " $streaming_runtime_log " ]; then
203+ rm -f " $streaming_runtime_log "
204+ fi
164205}
165206trap cleanup EXIT
166207
@@ -224,9 +265,12 @@ TEMPLATE
224265
225266run_streaming () {
226267 platform=" linux/$arch "
268+ api_image=" public.ecr.aws/amazonlinux/amazonlinux:2023"
269+ runtime_image=" public.ecr.aws/lambda/provided:al2023"
227270 streaming_dir=$( mktemp -d)
228271 streaming_code_dir=$( mktemp -d)
229272 streaming_request=" $streaming_dir /response_request.txt"
273+ streaming_next_request=" $streaming_dir /next_request.txt"
230274 streaming_event=" $streaming_dir /event.json"
231275 streaming_script=" $streaming_dir /runtime_api.sh"
232276 streaming_request_id=" streaming-test-id"
@@ -245,7 +289,7 @@ exec /opt/bootstrap
245289BOOT
246290 chmod +x " $streaming_code_dir /function.sh" " $streaming_code_dir /bootstrap"
247291
248- cat << 'API ' > "$streaming_script"
292+ cat << 'API ' > "$streaming_script"
249293#!/bin/sh
250294set -eu
251295
@@ -254,45 +298,135 @@ event_file=${EVENT_FILE:-/data/event.json}
254298next_file=${NEXT_FILE:-/data/next_request.txt}
255299response_file=${RESPONSE_FILE:-/data/response_request.txt}
256300request_id=${REQUEST_ID:-streaming-test-id}
301+ handler=${HANDLER_FILE:-/data/handle_request.sh}
302+ log_file=${LOG_FILE:-/data/api.log}
257303
258304event=$(cat "$event_file")
259305length=$(printf '%s' "$event" | wc -c | tr -d ' ')
260306
261- {
262- printf 'HTTP/1.1 200 OK\r\n'
263- printf 'Lambda-Runtime-Aws-Request-Id: %s\r\n' "$request_id"
264- printf 'Lambda-Runtime-Deadline-Ms: 0\r\n'
265- printf 'Lambda-Runtime-Function-Response-Mode: streaming\r\n'
266- printf 'Content-Type: application/json\r\n'
267- printf 'Content-Length: %s\r\n' "$length"
268- printf 'Connection: close\r\n'
269- printf '\r\n'
270- printf '%s' "$event"
271- } | nc -l -p "$port" > "$next_file"
272-
273- {
274- printf 'HTTP/1.1 202 Accepted\r\n'
275- printf 'Content-Length: 0\r\n'
276- printf 'Connection: close\r\n'
277- printf '\r\n'
278- } | nc -l -p "$port" > "$response_file"
307+ cat <<'HANDLER' > "$handler"
308+ #!/bin/sh
309+ set -eu
310+
311+ request_line=""
312+ if IFS= read -r request_line; then
313+ :
314+ fi
315+ clean_request_line=$(printf '%s' "$request_line" | tr -d '\r')
316+ if [ -z "$clean_request_line" ]; then
317+ exit 0
318+ fi
319+
320+ printf '%s\n' "$clean_request_line" >> "$LOG_FILE"
321+
322+ path=$(printf '%s' "$clean_request_line" | awk '{print $2}')
323+ target=""
324+ case "$path" in
325+ /2018-06-01/runtime/invocation/next)
326+ target="$NEXT_FILE"
327+ ;;
328+ /2018-06-01/runtime/invocation/*/response)
329+ target="$RESPONSE_FILE"
330+ ;;
331+ *)
332+ target="$RESPONSE_FILE"
333+ ;;
334+ esac
335+
336+ printf '%s\n' "$clean_request_line" > "$target"
337+ while IFS= read -r line; do
338+ clean_line=$(printf '%s' "$line" | tr -d '\r')
339+ printf '%s\n' "$clean_line" >> "$target"
340+ [ -z "$clean_line" ] && break
341+ done
342+
343+ case "$path" in
344+ /2018-06-01/runtime/invocation/next)
345+ {
346+ printf 'HTTP/1.1 200 OK\r\n'
347+ printf 'Lambda-Runtime-Aws-Request-Id: %s\r\n' "$REQUEST_ID"
348+ printf 'Lambda-Runtime-Deadline-Ms: 0\r\n'
349+ printf 'Lambda-Runtime-Function-Response-Mode: streaming\r\n'
350+ printf 'Content-Type: application/json\r\n'
351+ printf 'Content-Length: %s\r\n' "$EVENT_LENGTH"
352+ printf 'Connection: close\r\n'
353+ printf '\r\n'
354+ printf '%s' "$EVENT_PAYLOAD"
355+ }
356+ ;;
357+ *)
358+ {
359+ printf 'HTTP/1.1 202 Accepted\r\n'
360+ printf 'Content-Length: 0\r\n'
361+ printf 'Connection: close\r\n'
362+ printf '\r\n'
363+ }
364+ ;;
365+ esac
366+
367+ case "$path" in
368+ /2018-06-01/runtime/invocation/next)
369+ exit 0
370+ ;;
371+ esac
372+
373+ while IFS= read -r line || [ -n "$line" ]; do
374+ clean_line=$(printf '%s' "$line" | tr -d '\r')
375+ printf '%s\n' "$clean_line" >> "$target"
376+ done
377+ HANDLER
378+ chmod +x "$handler"
379+
380+ if command -v ncat >/dev/null 2>&1; then
381+ NCHANDLER=$(command -v ncat)
382+ elif command -v nc >/dev/null 2>&1; then
383+ NCHANDLER=$(command -v nc)
384+ else
385+ printf '%s\n' "ncat/nc not available" >&2
386+ exit 1
387+ fi
388+
389+ export NEXT_FILE="$next_file"
390+ export RESPONSE_FILE="$response_file"
391+ export REQUEST_ID="$request_id"
392+ export EVENT_PAYLOAD="$event"
393+ export EVENT_LENGTH="$length"
394+ export LOG_FILE="$log_file"
395+
396+ while :; do
397+ "$NCHANDLER" -l -p "$port" -c "$handler"
398+ done
279399API
280400 chmod +x " $streaming_script "
281401
282402 streaming_network=" lambda-shell-runtime-streaming-$arch -$$ "
283403 streaming_api_container=" lambda-shell-runtime-streaming-api-$$ "
284404 streaming_runtime_container=" lambda-shell-runtime-streaming-runtime-$$ "
405+ streaming_api_log=$( mktemp)
406+ streaming_runtime_log=$( mktemp)
407+
408+ ensure_image " $api_image "
409+ ensure_image " $runtime_image "
285410
286411 docker network create " $streaming_network " > /dev/null
287- docker run -d --name " $streaming_api_container " \
412+ if ! docker run -d --name " $streaming_api_container " \
288413 --network " $streaming_network " \
289414 --platform " $platform " \
290415 -v " $streaming_dir :/data" \
291- busybox sh /data/runtime_api.sh > /dev/null
416+ " $api_image " sh -c \
417+ ' dnf -y install nmap-ncat >/dev/null 2>&1 || { echo "Failed to install nmap-ncat" >&2; exit 1; }; sh /data/runtime_api.sh' \
418+ > " $streaming_api_log " 2>&1 ; then
419+ cat " $streaming_api_log " >&2
420+ exit 1
421+ fi
292422
293- sleep 0.2
423+ if ! wait_for_container_port " $streaming_api_container " 9001 600; then
424+ docker logs " $streaming_api_container " >&2 || true
425+ printf ' %s\n' " Streaming API container did not become ready" >&2
426+ exit 1
427+ fi
294428
295- docker run -d --name " $streaming_runtime_container " \
429+ if ! docker run -d --name " $streaming_runtime_container " \
296430 --network " $streaming_network " \
297431 --platform " $platform " \
298432 -v " $layer_root :/opt:ro" \
@@ -301,32 +435,65 @@ API
301435 -e _HANDLER=" function.handler" \
302436 -e LAMBDA_TASK_ROOT=" /var/task" \
303437 --entrypoint /bin/sh \
304- public.ecr.aws/lambda/provided:al2023 \
305- -c ' if ! rpm -q curl-minimal >/dev/null 2>&1; then echo "curl-minimal not installed" >&2; exit 1; fi; curl --version >&2; exec /opt/bootstrap' > /dev/null
438+ " $runtime_image " \
439+ -c ' if ! rpm -q curl-minimal >/dev/null 2>&1; then echo "curl-minimal not installed" >&2; exit 1; fi; exec /opt/bootstrap' \
440+ > " $streaming_runtime_log " 2>&1 ; then
441+ cat " $streaming_runtime_log " >&2
442+ exit 1
443+ fi
306444
307- if ! wait_for_file " $streaming_request " ; then
445+ if ! wait_for_file " $streaming_request " 600 ; then
308446 docker logs " $streaming_runtime_container " >&2 || true
447+ docker logs " $streaming_api_container " >&2 || true
448+ docker inspect -f ' runtime status: {{.State.Status}} exit={{.State.ExitCode}}' " $streaming_runtime_container " >&2 2> /dev/null || true
449+ docker inspect -f ' api status: {{.State.Status}} exit={{.State.ExitCode}}' " $streaming_api_container " >&2 2> /dev/null || true
450+ if [ -f " $streaming_next_request " ]; then
451+ printf ' %s\n' " Captured next request:" >&2
452+ tr -d ' \r' < " $streaming_next_request " >&2 || true
453+ fi
454+ if [ -f " $streaming_dir /api.log" ]; then
455+ printf ' %s\n' " API request log:" >&2
456+ cat " $streaming_dir /api.log" >&2 || true
457+ fi
309458 printf ' %s\n' " Streaming response request was not captured" >&2
310459 exit 1
311460 fi
312461
313462 streaming_request_clean=$( mktemp)
314- tr -d ' \r' < " $streaming_request " > " $streaming_request_clean "
463+ {
464+ if [ -f " $streaming_request " ]; then
465+ cat " $streaming_request "
466+ fi
467+ if [ -f " $streaming_next_request " ]; then
468+ cat " $streaming_next_request "
469+ fi
470+ } | tr -d ' \r' > " $streaming_request_clean "
315471
316- if ! grep -F " POST /2018-06-01/runtime/invocation/${streaming_request_id} /response" " $streaming_request_clean " > /dev/null 2>&1 ; then
317- printf ' %s\n' " Streaming response used unexpected endpoint" >&2
472+ if ! grep -F " /2018-06-01/runtime/invocation/${streaming_request_id} /response" " $streaming_request_clean " > /dev/null 2>&1 ; then
473+ request_line=$( head -n 1 " $streaming_request_clean " || true)
474+ printf ' %s\n' " Streaming response used unexpected endpoint: $request_line " >&2
475+ printf ' %s\n' " Captured requests:" >&2
476+ cat " $streaming_request_clean " >&2 || true
477+ docker logs " $streaming_runtime_container " >&2 || true
478+ docker logs " $streaming_api_container " >&2 || true
318479 exit 1
319480 fi
320481 if ! grep -i -F " Lambda-Runtime-Function-Response-Mode: streaming" " $streaming_request_clean " > /dev/null 2>&1 ; then
321482 printf ' %s\n' " Streaming response header missing response mode" >&2
483+ printf ' %s\n' " Captured requests:" >&2
484+ cat " $streaming_request_clean " >&2 || true
322485 exit 1
323486 fi
324487 if ! grep -i -F " Trailer: Lambda-Runtime-Function-Error-Type, Lambda-Runtime-Function-Error-Body" " $streaming_request_clean " > /dev/null 2>&1 ; then
325488 printf ' %s\n' " Streaming response header missing error trailers" >&2
489+ printf ' %s\n' " Captured requests:" >&2
490+ cat " $streaming_request_clean " >&2 || true
326491 exit 1
327492 fi
328493 if ! grep -F " $streaming_payload " " $streaming_request_clean " > /dev/null 2>&1 ; then
329494 printf ' %s\n' " Streaming response payload not captured" >&2
495+ printf ' %s\n' " Captured requests:" >&2
496+ cat " $streaming_request_clean " >&2 || true
330497 exit 1
331498 fi
332499}
0 commit comments