Skip to content

Commit 73d89f9

Browse files
committed
xinetdhttpservice.sh now reads in HTTP POST data. README is updated.
1 parent e7de6d5 commit 73d89f9

File tree

2 files changed

+210
-64
lines changed

2 files changed

+210
-64
lines changed

README.md

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ section.
2929
# If something unhealthy was detected, then:
3030
decrease_health_value
3131

32-
# display response
32+
# display health value response, and exit
3333
display_health_value
34+
35+
# send a http_response of 200
3436
http_response 200 "Success"
3537

3638
# End of program
@@ -44,6 +46,10 @@ http_response 200 "Success"
4446

4547
```bash
4648
linux$ xinetdhttpservice.sh --help
49+
xinetd_http_service 0.2
50+
https://github.com/rglaue/xinetd_bash_http_service
51+
Copyright (C) 2018 Russell Glaue, CAIT, WIU <http://www.cait.org>
52+
4753
Usage: xinetd_http_service [options]
4854
Description: bash script called by xinetd to service a HTTP request; a farmework for reporting on health
4955

@@ -71,6 +77,8 @@ Examples:
7177
7278
### Test to see how HTTP headers are parsed
7379
80+
#### HTTP GET
81+
7482
```bash
7583
linux$ echo "GET /test123?var1=val1 HTTP/1.0" | xinetdhttpservice.sh --http-status --show-headers
7684
HTTP/1.1 200 OK
@@ -86,6 +94,83 @@ HTTP_REQ_METHOD=GET
8694
HTTP_REQ_URI_PARAMS=var1=val1
8795
```
8896
97+
#### HTTP POST
98+
99+
```bash
100+
linux$ xinetdhttpservice.sh --show-headers <<HTTP_EOF
101+
POST /weight-value?max-weight=200 HTTP/1.1
102+
User-Agent: noagent/1.0
103+
Host: 127.0.0.1:8080
104+
Accept: */*
105+
Content-Length: 78
106+
Content-Type: application/x-www-form-urlencoded
107+
108+
{
109+
"type": "json",
110+
"key": "animal",
111+
"color": "brown",
112+
"name": "bear"
113+
}
114+
HTTP_EOF
115+
HTTP/1.1 200 OK
116+
Content-Type: text/plain
117+
Connection: close
118+
Content-Length: 515
119+
120+
HTTP_CONTENT_LENGTH=78
121+
HTTP_USER_AGENT=noagent/1.0
122+
HTTP_REQ_VERSION=HTTP/1.1
123+
HTTP_POST_CONTENT={
124+
HTTP_ACCEPT=*/*
125+
HTTP_CONTENT_TYPE=application/x-www-form-urlencoded
126+
HTTP_REQUEST=POST /weight-value?max-weight=200 HTTP/1.1
127+
HTTP_REQ_URI=/weight-value?max-weight=200
128+
HTTP_REQ_URI_PATH=/weight-value
129+
HTTP_REQ_METHOD=POST
130+
HTTP_REQ_URI_PARAMS=max-weight=200
131+
HTTP_SERVER=127.0.0.1:8080
132+
--BEGIN:HTTP_POST_CONTENT--
133+
{
134+
"type": "json",
135+
"key": "animal",
136+
"color": "brown",
137+
"name": "bear"
138+
}
139+
140+
--END:HTTP_POST_CONTENT--
141+
```
142+
143+
#### HTTP POST Config: MAX_HTTP_POST_LENGTH
144+
145+
At the top of the xinetdhttpservice.sh bash script, there is a global variable
146+
that define the maximum allowed length of posted data. Posted data that has a
147+
length greater than this will be cut off.
148+
149+
```bash
150+
MAX_HTTP_POST_LENGTH=200
151+
```
152+
153+
#### HTTP POST Config: READ_BUFFER_LENGTH
154+
155+
If a non-compliant HTTP client is posting data that is shorter than the
156+
Content-Length, then the READ_BUFFER_LENGTH should be set to 1. By default
157+
this value is the size of the Content-Length, which is more efficient.
158+
159+
```bash
160+
# If the value of Content-Length is greater than the actual content, then
161+
# read will timeout and never allow the collection from standard input.
162+
# This is overcome by reading one character at a time.
163+
#READ_BUFFER_LENGTH=1
164+
# If you are sure the value of Content-Length always equals the length of the
165+
# content, then all of standard input can be read in at one time
166+
READ_BUFFER_LENGTH=$DATA_LENGTH
167+
```
168+
169+
Note: The maximum length of posted data that is accepted is the Content-Length
170+
or the MAX_HTTP_POST_LENGTH, whichever is shorter. If the HTTP client is
171+
posting data, yet provides a Content-Length of 0, no data will be read in.
172+
173+
89174
### Test the HTTP output
90175
91176
```bash

xinetdhttpservice.sh

Lines changed: 124 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,66 +7,9 @@
77
PROG=xinetd_http_service
88
DESCRIPTION="bash script called by xinetd to service a HTTP request; a farmework for reporting on health"
99
SYNOPSIS="${PROG} [options]"
10-
VERSION=0.1
11-
LASTMOD=20180621
12-
13-
#
14-
# Read the HTTP headers from standard input, and parse and store their
15-
# values in environment variables.
16-
#
17-
while read -t 0.01 line; do
18-
if [ -z "$line" ]; then break; fi
19-
if echo "${line}" | grep -qi "^GET\|POST\|PUT\|DELETE"; then
20-
# GET /test123?r=123 HTTP/1.1
21-
export HTTP_REQUEST="${line}"
22-
export HTTP_REQ_METHOD="$(echo ${line}|cut -d" " -f 1)"
23-
export HTTP_REQ_URI="$(echo ${line}|cut -d" " -f 2)"
24-
export HTTP_REQ_URI_PATH="$(echo ${HTTP_REQ_URI}|cut -d"?" -f 1)"
25-
if echo "$HTTP_REQ_URI"|grep -q '?'; then
26-
export HTTP_REQ_URI_PARAMS="$(echo ${HTTP_REQ_URI}|cut -d"?" -f 2-)"
27-
else
28-
export HTTP_REQ_URI_PARAMS=""
29-
fi
30-
export HTTP_REQ_VERSION="$(echo ${line}|cut -d" " -f 3-)"
31-
elif echo "${line}" | grep -qi "^User-Agent:"; then
32-
# User-Agent: curl/7.29.0
33-
export HTTP_USER_AGENT="$(echo ${line}|cut -d" " -f 2-)"
34-
elif echo "${line}" | grep -qi "^Host:"; then
35-
# Host: 0.0.0.0:8081
36-
export HTTP_SERVER="$(echo ${line}|cut -d" " -f 2-)"
37-
elif echo "${line}" | grep -qi "^Accept::"; then
38-
# Accept: */*
39-
export HTTP_ACCEPT="$(echo ${line}|cut -d" " -f 2-)"
40-
else
41-
break
42-
fi
43-
done
44-
45-
#
46-
# A function to parse HTTP_REQ_URI_PARAMS and return the value of a given
47-
# parameter name
48-
# Example:
49-
# param_value=$(get_http_req_uri_params_value "param_name")
50-
# if [ "$?" -eq 1 ]; then echo "param_name" not provided; fi
51-
#
52-
get_http_req_uri_params_value () {
53-
# Example: "a=123&b=456&c&d=789"
54-
PARAM_NAME=$1
55-
IFS='&' read -r -a params <<< "$HTTP_REQ_URI_PARAMS"
56-
for element in "${params[@]}"; do
57-
element_name=$(echo $element | cut -d"=" -f 1)
58-
if [ "$element_name" == "$PARAM_NAME" ]; then
59-
if echo "$element" | grep -q "="; then
60-
element_value=$(echo $element | cut -d"=" -f 2-)
61-
echo "$element_value"
62-
else
63-
echo ""
64-
fi
65-
exit 0
66-
fi
67-
done
68-
exit 1
69-
}
10+
VERSION=0.2
11+
LASTMOD=20180822
12+
MAX_HTTP_POST_LENGTH=200
7013

7114
#
7215
# Handle the program's usage documentation
@@ -77,6 +20,8 @@ print_usage () {
7720
}
7821

7922
print_help () {
23+
print_version
24+
echo
8025
print_usage
8126
cat << EOF
8227
@@ -102,6 +47,7 @@ EOF
10247

10348
print_version () {
10449
echo "$PROG $VERSION"
50+
echo "https://github.com/rglaue/xinetd_bash_http_service"
10551
echo "Copyright (C) 2018 Russell Glaue, CAIT, WIU <http://www.cait.org>"
10652
}
10753

@@ -110,6 +56,7 @@ print_version () {
11056
#
11157

11258
# Set the default values before parsing the parameters
59+
: ${VERBOSE:=0}
11360
: ${OPT_HTTP_STATUS:=0}
11461
: ${OPT_HEALTH_VALUE:=0}
11562
: ${OPT_WEIGHT_VALUE:=0}
@@ -154,6 +101,117 @@ while true ; do
154101
esac
155102
done
156103

104+
#
105+
# Read the HTTP headers from standard input, and parse and store their
106+
# values in environment variables.
107+
#
108+
while read -t 0.01 line; do
109+
line=${line//$'\r'}
110+
if [ $VERBOSE -ge 1 ]; then
111+
echo "H: $line"
112+
fi
113+
if [ -z "$line" ]; then break; fi
114+
if echo "${line}" | grep -qi "^GET\|POST\|PUT\|DELETE"; then
115+
# GET /test123?r=123 HTTP/1.1
116+
export HTTP_REQUEST="${line}"
117+
export HTTP_REQ_METHOD="$(echo "${line}"|cut -d" " -f 1)"
118+
export HTTP_REQ_URI="$(echo "${line}"|cut -d" " -f 2)"
119+
export HTTP_REQ_URI_PATH="$(echo "${HTTP_REQ_URI}"|cut -d"?" -f 1)"
120+
if echo "$HTTP_REQ_URI"|grep -q '?'; then
121+
export HTTP_REQ_URI_PARAMS="$(echo "${HTTP_REQ_URI}"|cut -d"?" -f 2-)"
122+
else
123+
export HTTP_REQ_URI_PARAMS=""
124+
fi
125+
export HTTP_REQ_VERSION="$(echo "${line}"|cut -d" " -f 3-)"
126+
elif echo "${line}" | grep -qi "^User-Agent:"; then
127+
# User-Agent: curl/7.29.0
128+
export HTTP_USER_AGENT="$(echo "${line}"|cut -d" " -f 2-)"
129+
elif echo "${line}" | grep -qi "^Host:"; then
130+
# Host: 0.0.0.0:8081
131+
export HTTP_SERVER="$(echo "${line}"|cut -d" " -f 2-)"
132+
elif echo "${line}" | grep -qi "^Accept:"; then
133+
# Accept: */*
134+
export HTTP_ACCEPT="$(echo "${line}"|cut -d" " -f 2-)"
135+
#continue
136+
elif echo "${line}" | grep -qi "^Content-Length:"; then
137+
# Content-Length: 5
138+
export HTTP_CONTENT_LENGTH="$(echo "${line}"|cut -d" " -f 2-)"
139+
elif echo "${line}" | grep -qi "^Content-Type:"; then
140+
# Content-Type: application/x-www-form-urlencoded
141+
export HTTP_CONTENT_TYPE="$(echo "${line}"|cut -d" " -f 2-)"
142+
elif [ ${#line} -ge 1 ]; then
143+
# <any header>
144+
continue
145+
else
146+
break
147+
#continue
148+
fi
149+
done
150+
151+
#
152+
# Read the HTTP POST data from standard input
153+
# This does not support a Content-type of multipart/mixed
154+
# This does not support chunking. It expects, and only allows, posted data to
155+
# be the size of the Content-Length.
156+
#
157+
if [ "${HTTP_REQ_METHOD}" == "POST" ] && [ ${HTTP_CONTENT_LENGTH} -ge 1 ]; then
158+
export HTTP_POST_CONTENT=""
159+
DATA_LENGTH=$HTTP_CONTENT_LENGTH
160+
if [ ${DATA_LENGTH} -gt ${MAX_HTTP_POST_LENGTH} ]; then
161+
DATA_LENGTH=$MAX_HTTP_POST_LENGTH
162+
fi
163+
# If the value of Content-Length is greater than the actual content, then
164+
# read will timeout and never allow the collection from standard input.
165+
# This is overcome by reading one character at a time.
166+
#READ_BUFFER_LENGTH=1
167+
# If you are sure the value of Content-Length always equals the length of the
168+
# content, then all of standard input can be read in at one time
169+
READ_BUFFER_LENGTH=$DATA_LENGTH
170+
#
171+
# Read POST data via standard input
172+
while IFS= read -N $READ_BUFFER_LENGTH -r -t 0.01 post_buffer; do
173+
let "DATA_LENGTH = DATA_LENGTH - READ_BUFFER_LENGTH"
174+
HTTP_POST_CONTENT="${HTTP_POST_CONTENT}${post_buffer}"
175+
# Stop reading if we reach the content length, max length, or expected length
176+
if [ ${#HTTP_POST_CONTENT} -ge ${HTTP_CONTENT_LENGTH} ]; then
177+
break;
178+
elif [ ${#HTTP_POST_CONTENT} -ge ${MAX_HTTP_POST_LENGTH} ]; then
179+
break;
180+
elif [ ${DATA_LENGTH} -le 0 ]; then
181+
break;
182+
fi
183+
done
184+
if [ $VERBOSE -ge 1 ]; then
185+
echo -e "D: $HTTP_POST_CONTENT"
186+
fi
187+
fi
188+
189+
#
190+
# A function to parse HTTP_REQ_URI_PARAMS and return the value of a given
191+
# parameter name
192+
# Example:
193+
# param_value=$(get_http_req_uri_params_value "param_name")
194+
# if [ "$?" -eq 1 ]; then echo "param_name" not provided; fi
195+
#
196+
get_http_req_uri_params_value () {
197+
# Example: "a=123&b=456&c&d=789"
198+
PARAM_NAME=$1
199+
IFS='&' read -r -a params <<< "$HTTP_REQ_URI_PARAMS"
200+
for element in "${params[@]}"; do
201+
element_name="$(echo "$element" | cut -d"=" -f 1)"
202+
if [ "$element_name" == "$PARAM_NAME" ]; then
203+
if echo "$element" | grep -q "="; then
204+
element_value="$(echo "$element" | cut -d"=" -f 2-)"
205+
echo "$element_value"
206+
else
207+
echo ""
208+
fi
209+
exit 0
210+
fi
211+
done
212+
exit 1
213+
}
214+
157215
#
158216
# Parse parameters from the HTTP request
159217
#
@@ -240,13 +298,15 @@ display_health_value () {
240298
fi
241299
}
242300

243-
244301
#
245302
# --show-headers
246303
# Show the HTTP headers as parsed into the environment variables
247304
#
248305
if [ $OPT_SHOW_HEADERS -eq 1 ]; then
249306
THIS_HEADERS="$(env | grep '^HTTP')"
307+
if [ ! -z "$HTTP_POST_CONTENT" ]; then
308+
THIS_HEADERS="${THIS_HEADERS}\n--BEGIN:HTTP_POST_CONTENT--\n${HTTP_POST_CONTENT}\n--END:HTTP_POST_CONTENT--\n"
309+
fi
250310
if echo $THIS_HEADERS | grep -q '^HTTP'; then
251311
http_response 200 "$THIS_HEADERS"
252312
else
@@ -265,9 +325,10 @@ fi
265325
# If something unhealthy was detected, then:
266326
decrease_health_value
267327

268-
# display response
328+
# display health value response, and exit
269329
display_health_value
330+
331+
# send a http_response of 200
270332
http_response 200 "Success"
271333

272334
# End of program
273-

0 commit comments

Comments
 (0)