pipe and grep in Docker container seems to get blocked - bash

I have a simple bash script as follows that is part of a docker image.
test.sh,
#!/bin/bash
set -e
logit() {
log_date=`date +"%F %T"`
echo "[$log_date][INFO] $1"
}
waitForServerToStart() {
while true; do
logit "Testing .... 1"
netstat -anpt
logit "Testing .... 2"
netstat -anpt | grep tcp
logit "Testing .... 3"
sleep 5
logit "Testing .... 4"
done
}
waitForServerToStart
run.sh,
#!/bin/sh
/test.sh &
# Run forever
while true; do sleep 5; done
Dockerfile,
FROM openjdk:8u191-jre-alpine3.9
COPY files/run.sh /
COPY files/test.sh /
CMD ["/run.sh"]
If I run this container I only get the following output which leads me to believe somehow grep and "pipe" seem to get blocked.
[2019-03-06 11:10:45][INFO] Testing .... 1
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 172.17.0.2:58278 xxx.xxx.xx.xx:443 FIN_WAIT2 -
[2019-03-06 11:10:45][INFO] Testing .... 2
Can someone please shed some light around this ?
It works fine If I comment out netstat -anpt | grep tcp. I would then see the subsequent log lines and it would also continue in the loop.
[2019-03-06 11:25:36][INFO] Testing .... 3
[2019-03-06 11:25:41][INFO] Testing .... 4

This one has me puzzled! But I have a solution for you:
Use awk instead of grep
In test.sh use this instead:
netstat -anpt | awk /tcp/
So that the file looks like this:
#!/bin/bash
set -e
logit() {
log_date=`date +"%F %T"`
echo "[$log_date][INFO] $1"
}
waitForServerToStart() {
while true; do
logit "Testing .... 1"
netstat -anpt
logit "Testing .... 2"
netstat -anpt | awk /tcp/
logit "Testing .... 3"
sleep 5
logit "Testing .... 4"
done
}
waitForServerToStart
For a reason that I cannot explain - grep will not return when reading from the pipe when invoked from the script. I created your container locally, ran it and entered it - and the command netstat -anpt | grep tcp runs just fine and exits. If you replace it with netstat -anpt | cat in your test.sh script, then it will also pass just fine.
I looked all over the place for the someone with an identical issue with grep in a container from the distro you are using, the version etc. - but came up empty handed.
I believe that it may have to do with grep waiting for a EOF character that never lands - but I am not sure.

Related

Getting an error for unexpected else in bash

[SOLVED]
I'm pretty new tho bash-/shell-scripting and trying setup a check for ip address on a server which gets about once a week a new ip.
The script will then send the new ip to the users.
My problem is, that I'm getting a syntax-error in the last if-else statement for "unexpected" else and can wrap my head around why.
My first iteration didn't use functions, but instead one multi lined if-else which got me the same error. The functions on their own seem to work just fine.
#!/bin/bash
# script to send the new server ip to the users
# get the recent ip address of the system
new_ip=$(ip route get 8.8.8.8 | awk -F"src " 'NR==1{split($2,a," ");print a[1]}')
file=old_ip.txt
function ip_mail(){
source $file
if [ $new_ip != $old_ip ]
then
# email-address changed for obvious reasons
mail -s "New Server IP" [hidden]#[hidden].com <<< "$new_ip"
echo "old_ip=$new_ip" > old_ip.txt
exit 0
fi
exit 0
}
function set_old(){
touch old_ip.txt
echo "old_ip=$new_ip" > old_ip.txt
exit 0
}
if [ $file ]
then
ip_mail()
else
set_old()
fi

Reply to IRC PING with PONG in bash script

So far I have a basic IRC Bot which I hope to be able to successfully PRIVMSG myself with but on the server I am testing on it requires a PONG response to PING.
I have absolutely no idea how to get around this. How do I reply to the PING?
My current code:
#!/bin/bash
function ircpreamble {
echo "NICK ${1}"
}
function privmsg {
TARGET=$1
sed -re "s/^(.*)\$/PRIVMSG ${TARGET} :\1/"
}
function delay {
while read LINE; do
sleep 2
echo $LINE
done
}
function messages {
msg=`cat pmmsg.txt`
echo $msg
}
function disconnect {
echo "QUIT goodbye :)"
}
(
nick=`cat randnick.txt`
pms=`cat pmnickname.txt`
ircpreamble "$nick";
messages | privmsg "$pms";
disconnect;
) | delay | nc irc.seersirc.net 6667
You first need to "catch" the server responses. This can be done like:
$ nc irc.seersirc.net 6667 |while read res;do echo "==>$res";done
==>:irc.seersirc.net NOTICE AUTH :*** Looking up your hostname...
==>:irc.seersirc.net NOTICE AUTH :*** Couldn't resolve your hostname; using your IP address instead
Since now you catch the response in a variable, you can filter out these responses and send the appropriate commands back to the server.
The problem is that when running netcat/telnet from a script a simple echo "PONG" is not sending the messages back to server but echo prints the messages locally in your terminal.
To send messages to the server you need either to pipe those messages to netcat in the beginning (the technique you already use) or to use some kind of expect script or to use a file to feed the netcat.
Regarding the PONG response, note that in order pong to be accepted by the server it must include the message sent by the server along with it's ping request.
Your server sends something like PING :D7AA1D1D (different every time) and thus the correct pong response is PONG :D7AA1D1D
As a result you can not just include a pong response in the first messages send in the beginning to netcat, since you don't know what is the ID that your server will sent to you along with the ping request.
This is a working draft script using a file to continuously feed the netcat:
rm .ircbot
touch .ircbot
prmnick="gv"
tail -f .ircbot |nc irc.seersirc.net 6667 |while read res
do
echo "==>$res"
if [[ "$res" == *"Couldn't resolve your hostname; using your IP address instead"* ]];then
sleep 2
echo "NICK gvgv" >>.ircbot
tail -n1 .ircbot #used just to display the last line of the helper file in my screen.
elif [[ "$res" == *"PING"* ]]; then
sleep 2
echo "$res" |sed 's/PING/PONG/' >>.ircbot
tail -n1 .ircbot
sleep 2
echo "USER gvgv 8 * :gvgv " >>.ircbot
tail -n1 .ircbot
sleep 2
echo "PRIVMSG $prmnick : hello from bot" >>.ircbot
tail -n1 .ircbot
fi
done
Tip: By opening a second terminal , you can manually "control" above bot by sending more commands to the .ircbot file (i.e $ echo "JOIN #channel" >>.ircbot) which will be also fed to netcat.
By the way, some web search about bash irc bots will return some useful results.
This is one easy bash script to use as an irc bot: https://github.com/Newbrict/bash-irc-bot/blob/master/bot.sh
Also , i found this useful IRC Over Telnet guide: http://archive.oreilly.com/pub/h/1963
Finally , this is an alternative using the /dev/tcp directly : https://gist.github.com/Wollw/3330337

use shell script to detect inside docker container [duplicate]

[Updated1] I have a shell which will change TCP kernel parameters in some functions, but now I need to make this shell run in Docker container, that means, the shell need to know it is running inside a container and stop configuring the kernel.
Now I'm not sure how to achieve that, here is the contents of /proc/self/cgroup inside the container:
9:hugetlb:/
8:perf_event:/
7:blkio:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct:/
2:cpu:/docker/25ef774c390558ad8c4e9a8590b6a1956231aae404d6a7aba4dde320ff569b8b
1:cpuset:/
Any flags above can I use to figure out if this process is running inside a container?
[Updated2]: I have also noticed Determining if a process runs inside lxc/Docker, but it seems not working in this case, the content in /proc/1/cgroup of my container is:
8:perf_event:/
7:blkio:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct:/
2:cpu:/docker/25ef774c390558ad8c4e9a8590b6a1956231aae404d6a7aba4dde320ff569b8b
1:cpuset:/
No /lxc/containerid
Docker creates .dockerenv and .dockerinit (removed in v1.11) files at the top of the container's directory tree so you might want to check if those exist.
Something like this should work.
#!/bin/bash
if [ -f /.dockerenv ]; then
echo "I'm inside matrix ;(";
else
echo "I'm living in real world!";
fi
To check inside a Docker container if you are inside a Docker container or not can be done via /proc/1/cgroup. As this post suggests you can to the following:
Outside a docker container all entries in /proc/1/cgroup end on / as you can see here:
vagrant#ubuntu-13:~$ cat /proc/1/cgroup
11:name=systemd:/
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:freezer:/
6:devices:/
5:memory:/
4:cpuacct:/
3:cpu:/
2:cpuset:/
Inside a Docker container some of the control groups will belong to Docker (or LXC):
vagrant#ubuntu-13:~$ docker run busybox cat /proc/1/cgroup
11:name=systemd:/
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:freezer:/
6:devices:/docker/3601745b3bd54d9780436faa5f0e4f72bb46231663bb99a6bb892764917832c2
5:memory:/
4:cpuacct:/
3:cpu:/docker/3601745b3bd54d9780436faa5f0e4f72bb46231663bb99a6bb892764917832c2
2:cpuset:/
We use the proc's sched (/proc/$PID/sched) to extract the PID of the process. The process's PID inside the container will differ then it's PID on the host (a non-container system).
For example, the output of /proc/1/sched on a container
will return:
root#33044d65037c:~# cat /proc/1/sched | head -n 1
bash (5276, #threads: 1)
While on a non-container host:
$ cat /proc/1/sched | head -n 1
init (1, #threads: 1)
This helps to differentiate if you are in a container or not. eg you can do:
if [[ ! $(cat /proc/1/sched | head -n 1 | grep init) ]]; then {
echo in docker
} else {
echo not in docker
} fi
Using Environment Variables
For my money, I prefer to set an environment variable inside the docker image that can then be detected by the application.
For example, this is the start of a demo Dockerfile config:
FROM node:12.20.1 as base
ENV DOCKER_RUNNING=true
RUN yarn install --production
RUN yarn build
The second line sets an envar called DOCKER_RUNNING that is then easy to detect. The issue with this is that in a multi-stage build, you will have to repeat the ENV line every time you FROM off of an external image. For example, you can see that I FROM off of node:12.20.1, which includes a lot of extra stuff (git, for example). Later on in my Dockerfile I then COPY things over to a new image based on node:12.20.1-slim, which is much smaller:
FROM node:12.20.1-slim as server
ENV DOCKER_RUNNING=true
EXPOSE 3000
COPY --from=base /build /build
CMD ["node", "server.js"]
Even though this image target server is in the same Dockerfile, it requires the ENV var to be defined again because it has a different base image.
If you make use of Docker-Compose, you could instead easily define an envar there. For example, your docker-compose.yml file could look like this:
version: "3.8"
services:
nodeserver:
image: michaeloryl/stackdemo
environment:
- NODE_ENV=production
- DOCKER_RUNNING=true
Thomas' solution as code:
running_in_docker() {
(awk -F/ '$2 == "docker"' /proc/self/cgroup | read non_empty_input)
}
Note
The read with a dummy variable is a simple idiom for Does this produce any output?. It's a compact method for turning a possibly verbose grep or awk into a test of a pattern.
Additional note on read
What works for me is to check for the inode number of the '/.'
Inside the docker, its a very high number.
Outside the docker, its a very low number like '2'.
I reckon this approach would also depend on the FileSystem being used.
Example
Inside the docker:
# ls -ali / | sed '2!d' |awk {'print $1'}
1565265
Outside the docker
$ ls -ali / | sed '2!d' |awk {'print $1'}
2
In a script:
#!/bin/bash
INODE_NUM=`ls -ali / | sed '2!d' |awk {'print $1'}`
if [ $INODE_NUM == '2' ];
then
echo "Outside the docker"
else
echo "Inside the docker"
fi
We needed to exclude processes running in containers, but instead of checking for just docker cgroups we decided to compare /proc/<pid>/ns/pid to the init system at /proc/1/ns/pid. Example:
pid=$(ps ax | grep "[r]edis-server \*:6379" | awk '{print $1}')
if [ $(readlink "/proc/$pid/ns/pid") == $(readlink /proc/1/ns/pid) ]; then
echo "pid $pid is the same namespace as init system"
else
echo "pid $pid is in a different namespace as init system"
fi
Or in our case we wanted a one liner that generates an error if the process is NOT in a container
bash -c "test -h /proc/4129/ns/pid && test $(readlink /proc/4129/ns/pid) != $(readlink /proc/1/ns/pid)"
which we can execute from another process and if the exit code is zero then the specified PID is running in a different namespace.
golang code, via the /proc/%s/cgroup to check a process in a docker,include the k8s cluster
func GetContainerID(pid int32) string {
cgroupPath := fmt.Sprintf("/proc/%s/cgroup", strconv.Itoa(int(pid)))
return getContainerID(cgroupPath)
}
func GetImage(containerId string) string {
if containerId == "" {
return ""
}
image, ok := containerImage[containerId]
if ok {
return image
} else {
return ""
}
}
func getContainerID(cgroupPath string) string {
containerID := ""
content, err := ioutil.ReadFile(cgroupPath)
if err != nil {
return containerID
}
lines := strings.Split(string(content), "\n")
for _, line := range lines {
field := strings.Split(line, ":")
if len(field) < 3 {
continue
}
cgroup_path := field[2]
if len(cgroup_path) < 64 {
continue
}
// Non-systemd Docker
//5:net_prio,net_cls:/docker/de630f22746b9c06c412858f26ca286c6cdfed086d3b302998aa403d9dcedc42
//3:net_cls:/kubepods/burstable/pod5f399c1a-f9fc-11e8-bf65-246e9659ebfc/9170559b8aadd07d99978d9460cf8d1c71552f3c64fefc7e9906ab3fb7e18f69
pos := strings.LastIndex(cgroup_path, "/")
if pos > 0 {
id_len := len(cgroup_path) - pos - 1
if id_len == 64 {
//p.InDocker = true
// docker id
containerID = cgroup_path[pos+1 : pos+1+64]
// logs.Debug("pid:%v in docker id:%v", pid, id)
return containerID
}
}
// systemd Docker
//5:net_cls:/system.slice/docker-afd862d2ed48ef5dc0ce8f1863e4475894e331098c9a512789233ca9ca06fc62.scope
docker_str := "docker-"
pos = strings.Index(cgroup_path, docker_str)
if pos > 0 {
pos_scope := strings.Index(cgroup_path, ".scope")
id_len := pos_scope - pos - len(docker_str)
if pos_scope > 0 && id_len == 64 {
containerID = cgroup_path[pos+len(docker_str) : pos+len(docker_str)+64]
return containerID
}
}
}
return containerID
}
Based on Dan Walsh's comment about using SELinux ps -eZ | grep container_t, but without requiring ps to be installed:
$ podman run --rm fedora:31 cat /proc/1/attr/current
system_u:system_r:container_t:s0:c56,c299
$ podman run --rm alpine cat /proc/1/attr/current
system_u:system_r:container_t:s0:c558,c813
$ docker run --rm fedora:31 cat /proc/1/attr/current
system_u:system_r:container_t:s0:c8,c583
$ cat /proc/1/attr/current
system_u:system_r:init_t:s0
This just tells you you're running in a container, but not which runtime.
Didn't check other container runtimes but https://opensource.com/article/18/2/understanding-selinux-labels-container-runtimes provides more info and suggests this is widely used, might also work for rkt and lxc?
What works for me, as long as I know the system programs/scrips will be running on, is confirming if what's running with PID 1 is systemd (or equivalent). If not, that's a container.
And this should be true for any linux container, not only docker.
Had the need for this capability in 2022 on macOS and only the answer by #at0S still works from all the other options.
/proc/1/cgroup only has the root directory in a container unless configured otherwise
/proc/1/sched showed the same in-container process number. The name was different (bash) but that's not very portable.
Environment variables work if you configure your container yourself, but none of the default environment variables helped
I did find an option not listed in the other answers: /proc/1/mounts included an overlay filesystem with "docker" in its path.

How to check if a process is running inside docker container?

[Updated1] I have a shell which will change TCP kernel parameters in some functions, but now I need to make this shell run in Docker container, that means, the shell need to know it is running inside a container and stop configuring the kernel.
Now I'm not sure how to achieve that, here is the contents of /proc/self/cgroup inside the container:
9:hugetlb:/
8:perf_event:/
7:blkio:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct:/
2:cpu:/docker/25ef774c390558ad8c4e9a8590b6a1956231aae404d6a7aba4dde320ff569b8b
1:cpuset:/
Any flags above can I use to figure out if this process is running inside a container?
[Updated2]: I have also noticed Determining if a process runs inside lxc/Docker, but it seems not working in this case, the content in /proc/1/cgroup of my container is:
8:perf_event:/
7:blkio:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct:/
2:cpu:/docker/25ef774c390558ad8c4e9a8590b6a1956231aae404d6a7aba4dde320ff569b8b
1:cpuset:/
No /lxc/containerid
Docker creates .dockerenv and .dockerinit (removed in v1.11) files at the top of the container's directory tree so you might want to check if those exist.
Something like this should work.
#!/bin/bash
if [ -f /.dockerenv ]; then
echo "I'm inside matrix ;(";
else
echo "I'm living in real world!";
fi
To check inside a Docker container if you are inside a Docker container or not can be done via /proc/1/cgroup. As this post suggests you can to the following:
Outside a docker container all entries in /proc/1/cgroup end on / as you can see here:
vagrant#ubuntu-13:~$ cat /proc/1/cgroup
11:name=systemd:/
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:freezer:/
6:devices:/
5:memory:/
4:cpuacct:/
3:cpu:/
2:cpuset:/
Inside a Docker container some of the control groups will belong to Docker (or LXC):
vagrant#ubuntu-13:~$ docker run busybox cat /proc/1/cgroup
11:name=systemd:/
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:freezer:/
6:devices:/docker/3601745b3bd54d9780436faa5f0e4f72bb46231663bb99a6bb892764917832c2
5:memory:/
4:cpuacct:/
3:cpu:/docker/3601745b3bd54d9780436faa5f0e4f72bb46231663bb99a6bb892764917832c2
2:cpuset:/
We use the proc's sched (/proc/$PID/sched) to extract the PID of the process. The process's PID inside the container will differ then it's PID on the host (a non-container system).
For example, the output of /proc/1/sched on a container
will return:
root#33044d65037c:~# cat /proc/1/sched | head -n 1
bash (5276, #threads: 1)
While on a non-container host:
$ cat /proc/1/sched | head -n 1
init (1, #threads: 1)
This helps to differentiate if you are in a container or not. eg you can do:
if [[ ! $(cat /proc/1/sched | head -n 1 | grep init) ]]; then {
echo in docker
} else {
echo not in docker
} fi
Using Environment Variables
For my money, I prefer to set an environment variable inside the docker image that can then be detected by the application.
For example, this is the start of a demo Dockerfile config:
FROM node:12.20.1 as base
ENV DOCKER_RUNNING=true
RUN yarn install --production
RUN yarn build
The second line sets an envar called DOCKER_RUNNING that is then easy to detect. The issue with this is that in a multi-stage build, you will have to repeat the ENV line every time you FROM off of an external image. For example, you can see that I FROM off of node:12.20.1, which includes a lot of extra stuff (git, for example). Later on in my Dockerfile I then COPY things over to a new image based on node:12.20.1-slim, which is much smaller:
FROM node:12.20.1-slim as server
ENV DOCKER_RUNNING=true
EXPOSE 3000
COPY --from=base /build /build
CMD ["node", "server.js"]
Even though this image target server is in the same Dockerfile, it requires the ENV var to be defined again because it has a different base image.
If you make use of Docker-Compose, you could instead easily define an envar there. For example, your docker-compose.yml file could look like this:
version: "3.8"
services:
nodeserver:
image: michaeloryl/stackdemo
environment:
- NODE_ENV=production
- DOCKER_RUNNING=true
Thomas' solution as code:
running_in_docker() {
(awk -F/ '$2 == "docker"' /proc/self/cgroup | read non_empty_input)
}
Note
The read with a dummy variable is a simple idiom for Does this produce any output?. It's a compact method for turning a possibly verbose grep or awk into a test of a pattern.
Additional note on read
What works for me is to check for the inode number of the '/.'
Inside the docker, its a very high number.
Outside the docker, its a very low number like '2'.
I reckon this approach would also depend on the FileSystem being used.
Example
Inside the docker:
# ls -ali / | sed '2!d' |awk {'print $1'}
1565265
Outside the docker
$ ls -ali / | sed '2!d' |awk {'print $1'}
2
In a script:
#!/bin/bash
INODE_NUM=`ls -ali / | sed '2!d' |awk {'print $1'}`
if [ $INODE_NUM == '2' ];
then
echo "Outside the docker"
else
echo "Inside the docker"
fi
We needed to exclude processes running in containers, but instead of checking for just docker cgroups we decided to compare /proc/<pid>/ns/pid to the init system at /proc/1/ns/pid. Example:
pid=$(ps ax | grep "[r]edis-server \*:6379" | awk '{print $1}')
if [ $(readlink "/proc/$pid/ns/pid") == $(readlink /proc/1/ns/pid) ]; then
echo "pid $pid is the same namespace as init system"
else
echo "pid $pid is in a different namespace as init system"
fi
Or in our case we wanted a one liner that generates an error if the process is NOT in a container
bash -c "test -h /proc/4129/ns/pid && test $(readlink /proc/4129/ns/pid) != $(readlink /proc/1/ns/pid)"
which we can execute from another process and if the exit code is zero then the specified PID is running in a different namespace.
golang code, via the /proc/%s/cgroup to check a process in a docker,include the k8s cluster
func GetContainerID(pid int32) string {
cgroupPath := fmt.Sprintf("/proc/%s/cgroup", strconv.Itoa(int(pid)))
return getContainerID(cgroupPath)
}
func GetImage(containerId string) string {
if containerId == "" {
return ""
}
image, ok := containerImage[containerId]
if ok {
return image
} else {
return ""
}
}
func getContainerID(cgroupPath string) string {
containerID := ""
content, err := ioutil.ReadFile(cgroupPath)
if err != nil {
return containerID
}
lines := strings.Split(string(content), "\n")
for _, line := range lines {
field := strings.Split(line, ":")
if len(field) < 3 {
continue
}
cgroup_path := field[2]
if len(cgroup_path) < 64 {
continue
}
// Non-systemd Docker
//5:net_prio,net_cls:/docker/de630f22746b9c06c412858f26ca286c6cdfed086d3b302998aa403d9dcedc42
//3:net_cls:/kubepods/burstable/pod5f399c1a-f9fc-11e8-bf65-246e9659ebfc/9170559b8aadd07d99978d9460cf8d1c71552f3c64fefc7e9906ab3fb7e18f69
pos := strings.LastIndex(cgroup_path, "/")
if pos > 0 {
id_len := len(cgroup_path) - pos - 1
if id_len == 64 {
//p.InDocker = true
// docker id
containerID = cgroup_path[pos+1 : pos+1+64]
// logs.Debug("pid:%v in docker id:%v", pid, id)
return containerID
}
}
// systemd Docker
//5:net_cls:/system.slice/docker-afd862d2ed48ef5dc0ce8f1863e4475894e331098c9a512789233ca9ca06fc62.scope
docker_str := "docker-"
pos = strings.Index(cgroup_path, docker_str)
if pos > 0 {
pos_scope := strings.Index(cgroup_path, ".scope")
id_len := pos_scope - pos - len(docker_str)
if pos_scope > 0 && id_len == 64 {
containerID = cgroup_path[pos+len(docker_str) : pos+len(docker_str)+64]
return containerID
}
}
}
return containerID
}
Based on Dan Walsh's comment about using SELinux ps -eZ | grep container_t, but without requiring ps to be installed:
$ podman run --rm fedora:31 cat /proc/1/attr/current
system_u:system_r:container_t:s0:c56,c299
$ podman run --rm alpine cat /proc/1/attr/current
system_u:system_r:container_t:s0:c558,c813
$ docker run --rm fedora:31 cat /proc/1/attr/current
system_u:system_r:container_t:s0:c8,c583
$ cat /proc/1/attr/current
system_u:system_r:init_t:s0
This just tells you you're running in a container, but not which runtime.
Didn't check other container runtimes but https://opensource.com/article/18/2/understanding-selinux-labels-container-runtimes provides more info and suggests this is widely used, might also work for rkt and lxc?
What works for me, as long as I know the system programs/scrips will be running on, is confirming if what's running with PID 1 is systemd (or equivalent). If not, that's a container.
And this should be true for any linux container, not only docker.
Had the need for this capability in 2022 on macOS and only the answer by #at0S still works from all the other options.
/proc/1/cgroup only has the root directory in a container unless configured otherwise
/proc/1/sched showed the same in-container process number. The name was different (bash) but that's not very portable.
Environment variables work if you configure your container yourself, but none of the default environment variables helped
I did find an option not listed in the other answers: /proc/1/mounts included an overlay filesystem with "docker" in its path.

Minimal web server using netcat

I'm trying to set up a minimal web server using netcat (nc). When the browser calls up localhost:1500, for instance, it should show the result of a function (date in the example below, but eventually it'll be a python or c program that yields some data).
My little netcat web server needs to be a while true loop in bash, possibly as simple as this:
while true ; do echo -e "HTTP/1.1 200 OK\n\n $(date)" | nc -l -p 1500 ; done
When I try this the browser shows the currently available data during the moment when nc starts. I want the browser displays the data during the moment the browser requests it, though. How can I achieve this?
Try this:
while true ; do nc -l -p 1500 -c 'echo -e "HTTP/1.1 200 OK\n\n $(date)"'; done
The -cmakes netcat execute the given command in a shell, so you can use echo. If you don't need echo, use -e. For further information on this, try man nc. Note, that when using echo there is no way for your program (the date-replacement) to get the browser request. So you probably finally want to do something like this:
while true ; do nc -l -p 1500 -e /path/to/yourprogram ; done
Where yourprogram must do the protocol stuff like handling GET, sending HTTP 200 etc.
I had the problem where I wanted to return the result of executing a bash command:
$ while true; do { echo -e 'HTTP/1.1 200 OK\r\n'; sh test; } | nc -l 8080; done
NOTE:
This command was taken from: http://www.razvantudorica.com/08/web-server-in-one-line-of-bash
This executes a bash script and returns the result to a browser client connecting to the server running this command on port 8080.
My script does this:
$ nano test
#!/bin/bash
echo "************PRINT SOME TEXT***************\n"
echo "Hello World!!!"
echo "\n"
echo "Resources:"
vmstat -S M
echo "\n"
echo "Addresses:"
echo "$(ifconfig)"
echo "\n"
echo "$(gpio readall)"
and my web browser is showing
************PRINT SOME TEXT***************
Hello World!!!
Resources:
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 0 314 18 78 0 0 2 1 306 31 0 0 100 0
Addresses:
eth0 Link encap:Ethernet HWaddr b8:27:eb:86:e8:c5
inet addr:192.168.1.83 Bcast:192.168.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:27734 errors:0 dropped:0 overruns:0 frame:0
TX packets:26393 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1924720 (1.8 MiB) TX bytes:3841998 (3.6 MiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
GPIOs:
+----------+-Rev2-+------+--------+------+-------+
| wiringPi | GPIO | Phys | Name | Mode | Value |
+----------+------+------+--------+------+-------+
| 0 | 17 | 11 | GPIO 0 | IN | Low |
| 1 | 18 | 12 | GPIO 1 | IN | Low |
| 2 | 27 | 13 | GPIO 2 | IN | Low |
| 3 | 22 | 15 | GPIO 3 | IN | Low |
| 4 | 23 | 16 | GPIO 4 | IN | Low |
| 5 | 24 | 18 | GPIO 5 | IN | Low |
| 6 | 25 | 22 | GPIO 6 | IN | Low |
| 7 | 4 | 7 | GPIO 7 | IN | Low |
| 8 | 2 | 3 | SDA | IN | High |
| 9 | 3 | 5 | SCL | IN | High |
| 10 | 8 | 24 | CE0 | IN | Low |
| 11 | 7 | 26 | CE1 | IN | Low |
| 12 | 10 | 19 | MOSI | IN | Low |
| 13 | 9 | 21 | MISO | IN | Low |
| 14 | 11 | 23 | SCLK | IN | Low |
| 15 | 14 | 8 | TxD | ALT0 | High |
| 16 | 15 | 10 | RxD | ALT0 | High |
| 17 | 28 | 3 | GPIO 8 | ALT2 | Low |
| 18 | 29 | 4 | GPIO 9 | ALT2 | Low |
| 19 | 30 | 5 | GPIO10 | ALT2 | Low |
| 20 | 31 | 6 | GPIO11 | ALT2 | Low |
+----------+------+------+--------+------+-------+
Add -q 1 to the netcat command line:
while true; do
echo -e "HTTP/1.1 200 OK\n\n $(date)" | nc -l -p 1500 -q 1
done
The problem you are facing is that nc does not know when the web client is done with its request so it can respond to the request.
A web session should go something like this.
TCP session is established.
Browser Request Header: GET / HTTP/1.1
Browser Request Header: Host: www.google.com
Browser Request Header: \n #Note: Browser is telling Webserver that the request header is complete.
Server Response Header: HTTP/1.1 200 OK
Server Response Header: Content-Type: text/html
Server Response Header: Content-Length: 24
Server Response Header: \n #Note: Webserver is telling browser that response header is complete
Server Message Body: <html>sample html</html>
Server Message Body: \n #Note: Webserver is telling the browser that the requested resource is finished.
The server closes the TCP session.
Lines that begin with "\n" are simply empty lines without even a space and contain nothing more than a new line character.
I have my bash httpd launched by xinetd, xinetd tutorial. It also logs date, time, browser IP address, and the entire browser request to a log file, and calculates Content-Length for the Server header response.
user#machine:/usr/local/bin# cat ./bash_httpd
#!/bin/bash
x=0;
Log=$( echo -n "["$(date "+%F %T %Z")"] $REMOTE_HOST ")$(
while read I[$x] && [ ${#I[$x]} -gt 1 ];do
echo -n '"'${I[$x]} | sed -e's,.$,",'; let "x = $x + 1";
done ;
); echo $Log >> /var/log/bash_httpd
Message_Body=$(echo -en '<html>Sample html</html>')
echo -en "HTTP/1.0 200 OK\nContent-Type: text/html\nContent-Length: ${#Message_Body}\n\n$Message_Body"
To add more functionality, you could incorporate.
METHOD=$(echo ${I[0]} |cut -d" " -f1)
REQUEST=$(echo ${I[0]} |cut -d" " -f2)
HTTP_VERSION=$(echo ${I[0]} |cut -d" " -f3)
If METHOD = "GET" ]; then
case "$REQUEST" in
"/") Message_Body="HTML formatted home page stuff"
;;
/who) Message_Body="HTML formatted results of who"
;;
/ps) Message_Body="HTML formatted results of ps"
;;
*) Message_Body= "Error Page not found header and content"
;;
esac
fi
Happy bashing!
Another way to do this
while true; do (echo -e 'HTTP/1.1 200 OK\r\n'; echo -e "\n\tMy website has date function" ; echo -e "\t$(date)\n") | nc -lp 8080; done
Let's test it with 2 HTTP request using curl
In this example, 172.16.2.6 is the server IP Address.
Server Side
admin#server:~$ while true; do (echo -e 'HTTP/1.1 200 OK\r\n'; echo -e "\n\tMy website has date function" ; echo -e "\t$(date)\n") | nc -lp 8080; done
GET / HTTP/1.1 Host: 172.16.2.6:8080 User-Agent: curl/7.48.0 Accept:
*/*
GET / HTTP/1.1 Host: 172.16.2.6:8080 User-Agent: curl/7.48.0 Accept:
*/*
Client Side
user#client:~$ curl 172.16.2.6:8080
My website has date function
Tue Jun 13 18:00:19 UTC 2017
user#client:~$ curl 172.16.2.6:8080
My website has date function
Tue Jun 13 18:00:24 UTC 2017
user#client:~$
If you want to execute another command, feel free to replace $(date).
I had the same need/problem but nothing here worked for me (or I didn't understand everything), so this is my solution.
I post my minimal_http_server.sh (working with my /bin/bash (4.3.11) but not /bin/sh because of the redirection):
rm -f out
mkfifo out
trap "rm -f out" EXIT
while true
do
cat out | nc -l 1500 > >( # parse the netcat output, to build the answer redirected to the pipe "out".
export REQUEST=
while read -r line
do
line=$(echo "$line" | tr -d '\r\n')
if echo "$line" | grep -qE '^GET /' # if line starts with "GET /"
then
REQUEST=$(echo "$line" | cut -d ' ' -f2) # extract the request
elif [ -z "$line" ] # empty line / end of request
then
# call a script here
# Note: REQUEST is exported, so the script can parse it (to answer 200/403/404 status code + content)
./a_script.sh > out
fi
done
)
done
And my a_script.sh (with your need):
#!/bin/bash
echo -e "HTTP/1.1 200 OK\r"
echo "Content-type: text/html"
echo
date
mkfifo pipe;
while true ;
do
#use read line from pipe to make it blocks before request comes in,
#this is the key.
{ read line<pipe;echo -e "HTTP/1.1 200 OK\r\n";echo $(date);
} | nc -l -q 0 -p 8080 > pipe;
done
Here is a beauty of a little bash webserver, I found it online and forked a copy and spruced it up a bit - it uses socat or netcat I have tested it with socat -- it is self-contained in one-script and generates its own configuration file and favicon.
By default it will start up as a web enabled file browser yet is easily configured by the configuration file for any logic. For files it streams images and music (mp3's), video (mp4's, avi, etc) -- I have tested streaming various file types to Linux,Windows and Android devices including a smartwatch!
I think it streams better than VLC actually. I have found it useful for transferring files to remote clients who have no access beyond a web browser e.g. Android smartwatch without needing to worry about physically connecting to a USB port.
If you want to try it out just copy and paste it to a file named bashttpd, then start it up on the host with $> bashttpd -s
Then you can go to any other computer (presuming the firewall is not blocking inbound tcp connections to port 8080 -- the default port, you can change the port to whatever you want using the global variables at the top of the script). http://bashttpd_server_ip:8080
#!/usr/bin/env bash
#############################################################################
###########################################################################
### bashttpd v 1.12
###
### Original author: Avleen Vig, 2012
### Reworked by: Josh Cartwright, 2012
### Modified by: A.M.Danischewski, 2015
### Issues: If you find any issues leave me a comment at
### http://scriptsandoneliners.blogspot.com/2015/04/bashttpd-self-contained-bash-webserver.html
###
### This is a simple Bash based webserver. By default it will browse files and allows for
### retrieving binary files.
###
### It has been tested successfully to view and stream files including images, mp3s,
### mp4s and downloading files of any type including binary and compressed files via
### any web browser.
###
### Successfully tested on various browsers on Windows, Linux and Android devices (including the
### Android Smartwatch ZGPAX S8).
###
### It handles favicon requests by hardcoded favicon image -- by default a marathon
### runner; change it to whatever you want! By base64 encoding your favorit favicon
### and changing the global variable below this header.
###
### Make sure if you have a firewall it allows connections to the port you plan to
### listen on (8080 by default).
###
### By default this program will allow for the browsing of files from the
### computer where it is run.
###
### Make sure you are allowed connections to the port you plan to listen on
### (8080 by default). Then just drop it on a host machine (that has bash)
### and start it up like this:
###
### $192.168.1.101> bashttpd -s
###
### On the remote machine you should be able to browse and download files from the host
### server via any web browser by visiting:
###
### http://192.168.1.101:8080
###
#### This program requires (to work to full capacity) by default:
### socat or netcat (w/ '-e' option - on Ubuntu netcat-traditional)
### tree - useful for pretty directory listings
### If you are using socat, you can type: bashttpd -s
###
### to start listening on the LISTEN_PORT (default is 8080), you can change
### the port below.
### E.g. nc -lp 8080 -e ./bashttpd ## <-- If your nc has the -e option.
### E.g. nc.traditional -lp 8080 -e ./bashttpd
### E.g. bashttpd -s -or- socat TCP4-LISTEN:8080,fork EXEC:bashttpd
###
### Copyright (C) 2012, Avleen Vig <avleen#gmail.com>
###
### Permission is hereby granted, free of charge, to any person obtaining a copy of
### this software and associated documentation files (the "Software"), to deal in
### the Software without restriction, including without limitation the rights to
### use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
### the Software, and to permit persons to whom the Software is furnished to do so,
### subject to the following conditions:
###
### The above copyright notice and this permission notice shall be included in all
### copies or substantial portions of the Software.
###
### THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
### IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
### FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
### COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
### IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
### CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
###
###########################################################################
#############################################################################
### CHANGE THIS TO WHERE YOU WANT THE CONFIGURATION FILE TO RESIDE
declare -r BASHTTPD_CONF="/tmp/bashttpd.conf"
### CHANGE THIS IF YOU WOULD LIKE TO LISTEN ON A DIFFERENT PORT
declare -i LISTEN_PORT=8080
## If you are on AIX, IRIX, Solaris, or a hardened system redirecting
## to /dev/random will probably break, you can change it to /dev/null.
declare -a DUMP_DEV="/dev/random"
## Just base64 encode your favorite favicon and change this to whatever you want.
declare -r FAVICON="AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAADg4+3/srjc/5KV2P+ortn/xMrj/6Ch1P+Vl9f/jIzc/3572f+CgNr/fnzP/3l01f+Ih9r/h4TZ/8fN4//P1Oj/3uPr/7O+1v+xu9X/u8XY/9bi6v+UmdD/XV26/3F1x/+GitT/VVXC/3x/x/+HjNT/lp3Z/6633f/E0eD/2ePr/+bt8v/U4+v/0uLp/9Xj6//Z5e3/oKbX/0pJt/9maML/cHLF/3p8x//T3+n/3Ofu/9vo7//W5Oz/0uHq/9zn7f/j6vD/1OLs/8/f6P/R4Oj/1OPr/7jA4f9KSbf/Skm3/3p/yf/U4ez/1ePq/9rn7//Z5e3/0uHp/87e5//a5Ov/5Ovw/9Hf6v/T4uv/1OLp/9bj6/+kq9r/Skq3/0pJt/+cotb/zdnp/9jl7f/a5u//1+Ts/9Pi6v/O3ub/2uXr/+bt8P/Q3un/0eDq/9bj7P/Z5u7/r7jd/0tKt/9NTLf/S0u2/8zW6v/c5+//2+fv/9bj6//S4un/zt3m/9zm7P/k7PD/1OPr/9Li7P/V5Oz/2OXt/9jl7v+HjM3/lZvT/0tKt/+6w+L/2ebu/9fk7P/V4+v/0uHq/83d5v/a5ev/5ezw/9Pi6v/U4+z/1eXs/9bj6//b5+//vsjj/1hYvP9JSLb/horM/9nk7P/X5e3/1eTs/9Pi6v/P3uf/2eXr/+Tr7//O3+n/0uLr/9Xk7P/Y5e3/w8/k/7XA3/9JR7f/SEe3/2lrw//G0OX/1uLr/9Xi7P/T4ev/0N/o/9zn7f/k7PD/zN3p/8rd5v/T4ur/1ePt/5We0/+0w9//SEe3/0pKt/9OTrf/p7HZ/7fD3//T4uv/0N/o/9Hg6f/d5+3/5ezw/9Li6//T4uv/2ubu/8PQ5f9+hsr/ucff/4eOzv+Ei8z/rLja/8zc6P/I1+b/0OLq/8/f6P/Q4Oj/3eft/+bs8f/R4On/0+Lq/9Tj6v/T4Ov/wM7h/9Df6f/M2uf/z97q/9Dg6f/Q4On/1OPr/9Tj6//S4ur/0ODp/93o7f/n7vH/0N/o/8/f5//P3+b/2OXt/9zo8P/c6fH/zdjn/7fB3/+3weD/1eLs/9nn7//V5Oz/0+Lr/9Pi6//e6O7/5u3x/9Pi6v/S4en/0uLp/9Tj6//W4+v/3Ojw/9rm7v9vccT/wcvm/9rn7//X5Oz/0uHq/9Hg6f/S4er/3uju/+bt8f/R4On/0uHp/9Xk6//Y5u7/1OTs/9bk7P/W5Ov/XFy9/2lrwf/a5+//1uPr/9Pi6v/U4er/0eHq/93o7v/v8vT/5ezw/+bt8f/o7vL/6e/z/+jv8v/p7/L/6e/y/9XZ6//IzOX/6e7y/+nv8v/o7vL/5+7x/+ft8f/r8PP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
declare -i DEBUG=1
declare -i VERBOSE=0
declare -a REQUEST_HEADERS
declare REQUEST_URI=""
declare -a HTTP_RESPONSE=(
[200]="OK"
[400]="Bad Request"
[403]="Forbidden"
[404]="Not Found"
[405]="Method Not Allowed"
[500]="Internal Server Error")
declare DATE=$(date +"%a, %d %b %Y %H:%M:%S %Z")
declare -a RESPONSE_HEADERS=(
"Date: $DATE"
"Expires: $DATE"
"Server: Slash Bin Slash Bash"
)
function warn() { ((${VERBOSE})) && echo "WARNING: $#" >&2; }
function chk_conf_file() {
[ -r "${BASHTTPD_CONF}" ] || {
cat >"${BASHTTPD_CONF}" <<'EOF'
#
# bashttpd.conf - configuration for bashttpd
#
# The behavior of bashttpd is dictated by the evaluation
# of rules specified in this configuration file. Each rule
# is evaluated until one is matched. If no rule is matched,
# bashttpd will serve a 500 Internal Server Error.
#
# The format of the rules are:
# on_uri_match REGEX command [args]
# unconditionally command [args]
#
# on_uri_match:
# On an incoming request, the URI is checked against the specified
# (bash-supported extended) regular expression, and if encounters a match the
# specified command is executed with the specified arguments.
#
# For additional flexibility, on_uri_match will also pass the results of the
# regular expression match, ${BASH_REMATCH[#]} as additional arguments to the
# command.
#
# unconditionally:
# Always serve via the specified command. Useful for catchall rules.
#
# The following commands are available for use:
#
# serve_file FILE
# Statically serves a single file.
#
# serve_dir_with_tree DIRECTORY
# Statically serves the specified directory using 'tree'. It must be
# installed and in the PATH.
#
# serve_dir_with_ls DIRECTORY
# Statically serves the specified directory using 'ls -al'.
#
# serve_dir DIRECTORY
# Statically serves a single directory listing. Will use 'tree' if it is
# installed and in the PATH, otherwise, 'ls -al'
#
# serve_dir_or_file_from DIRECTORY
# Serves either a directory listing (using serve_dir) or a file (using
# serve_file). Constructs local path by appending the specified root
# directory, and the URI portion of the client request.
#
# serve_static_string STRING
# Serves the specified static string with Content-Type text/plain.
#
# Examples of rules:
#
# on_uri_match '^/issue$' serve_file "/etc/issue"
#
# When a client's requested URI matches the string '/issue', serve them the
# contents of /etc/issue
#
# on_uri_match 'root' serve_dir /
#
# When a client's requested URI has the word 'root' in it, serve up
# a directory listing of /
#
# DOCROOT=/var/www/html
# on_uri_match '/(.*)' serve_dir_or_file_from "$DOCROOT"
# When any URI request is made, attempt to serve a directory listing
# or file content based on the request URI, by mapping URI's to local
# paths relative to the specified "$DOCROOT"
#
#unconditionally serve_static_string 'Hello, world! You can configure bashttpd by modifying bashttpd.conf.'
DOCROOT=/
on_uri_match '/(.*)' serve_dir_or_file_from
# More about commands:
#
# It is possible to somewhat easily write your own commands. An example
# may help. The following example will serve "Hello, $x!" whenever
# a client sends a request with the URI /say_hello_to/$x:
#
# serve_hello() {
# add_response_header "Content-Type" "text/plain"
# send_response_ok_exit <<< "Hello, $2!"
# }
# on_uri_match '^/say_hello_to/(.*)$' serve_hello
#
# Like mentioned before, the contents of ${BASH_REMATCH[#]} are passed
# to your command, so its possible to use regular expression groups
# to pull out info.
#
# With this example, when the requested URI is /say_hello_to/Josh, serve_hello
# is invoked with the arguments '/say_hello_to/Josh' 'Josh',
# (${BASH_REMATCH[0]} is always the full match)
EOF
warn "Created bashttpd.conf using defaults. Please review and configure bashttpd.conf before running bashttpd again."
# exit 1
}
}
function recv() { ((${VERBOSE})) && echo "< $#" >&2; }
function send() { ((${VERBOSE})) && echo "> $#" >&2; echo "$*"; }
function add_response_header() { RESPONSE_HEADERS+=("$1: $2"); }
function send_response_binary() {
local code="$1"
local file="${2}"
local transfer_stats=""
local tmp_stat_file="/tmp/_send_response_$$_"
send "HTTP/1.0 $1 ${HTTP_RESPONSE[$1]}"
for i in "${RESPONSE_HEADERS[#]}"; do
send "$i"
done
send
if ((${VERBOSE})); then
## Use dd since it handles null bytes
dd 2>"${tmp_stat_file}" < "${file}"
transfer_stats=$(<"${tmp_stat_file}")
echo -en ">> Transferred: ${file}\n>> $(awk '/copied/{print}' <<< "${transfer_stats}")\n" >&2
rm "${tmp_stat_file}"
else
## Use dd since it handles null bytes
dd 2>"${DUMP_DEV}" < "${file}"
fi
}
function send_response() {
local code="$1"
send "HTTP/1.0 $1 ${HTTP_RESPONSE[$1]}"
for i in "${RESPONSE_HEADERS[#]}"; do
send "$i"
done
send
while IFS= read -r line; do
send "${line}"
done
}
function send_response_ok_exit() { send_response 200; exit 0; }
function send_response_ok_exit_binary() { send_response_binary 200 "${1}"; exit 0; }
function fail_with() { send_response "$1" <<< "$1 ${HTTP_RESPONSE[$1]}"; exit 1; }
function serve_file() {
local file="$1"
local CONTENT_TYPE=""
case "${file}" in
*\.css)
CONTENT_TYPE="text/css"
;;
*\.js)
CONTENT_TYPE="text/javascript"
;;
*)
CONTENT_TYPE=$(file -b --mime-type "${file}")
;;
esac
add_response_header "Content-Type" "${CONTENT_TYPE}"
CONTENT_LENGTH=$(stat -c'%s' "${file}")
add_response_header "Content-Length" "${CONTENT_LENGTH}"
## Use binary safe transfer method since text doesn't break.
send_response_ok_exit_binary "${file}"
}
function serve_dir_with_tree() {
local dir="$1" tree_vers tree_opts basehref x
## HTML 5 compatible way to avoid tree html from generating favicon
## requests in certain browsers, such as browsers in android smartwatches. =)
local no_favicon=" <link href=\"data:image/x-icon;base64,${FAVICON}\" rel=\"icon\" type=\"image/x-icon\" />"
local tree_page=""
local base_server_path="/${2%/}"
[ "$base_server_path" = "/" ] && base_server_path=".."
local tree_opts="--du -h -a --dirsfirst"
add_response_header "Content-Type" "text/html"
# The --du option was added in 1.6.0. "/${2%/*}"
read _ tree_vers x < <(tree --version)
tree_page=$(tree -H "$base_server_path" -L 1 "${tree_opts}" -D "${dir}")
tree_page=$(sed "5 i ${no_favicon}" <<< "${tree_page}")
[[ "${tree_vers}" == v1.6* ]]
send_response_ok_exit <<< "${tree_page}"
}
function serve_dir_with_ls() {
local dir="$1"
add_response_header "Content-Type" "text/plain"
send_response_ok_exit < \
<(ls -la "${dir}")
}
function serve_dir() {
local dir="$1"
# If `tree` is installed, use that for pretty output.
which tree &>"${DUMP_DEV}" && \
serve_dir_with_tree "$#"
serve_dir_with_ls "$#"
fail_with 500
}
function urldecode() { [ "${1%/}" = "" ] && echo "/" || echo -e "$(sed 's/%\([[:xdigit:]]\{2\}\)/\\\x\1/g' <<< "${1%/}")"; }
function serve_dir_or_file_from() {
local URL_PATH="${1}/${3}"
shift
URL_PATH=$(urldecode "${URL_PATH}")
[[ $URL_PATH == *..* ]] && fail_with 400
# Serve index file if exists in requested directory
[[ -d "${URL_PATH}" && -f "${URL_PATH}/index.html" && -r "${URL_PATH}/index.html" ]] && \
URL_PATH="${URL_PATH}/index.html"
if [[ -f "${URL_PATH}" ]]; then
[[ -r "${URL_PATH}" ]] && \
serve_file "${URL_PATH}" "$#" || fail_with 403
elif [[ -d "${URL_PATH}" ]]; then
[[ -x "${URL_PATH}" ]] && \
serve_dir "${URL_PATH}" "$#" || fail_with 403
fi
fail_with 404
}
function serve_static_string() {
add_response_header "Content-Type" "text/plain"
send_response_ok_exit <<< "$1"
}
function on_uri_match() {
local regex="$1"
shift
[[ "${REQUEST_URI}" =~ $regex ]] && \
"$#" "${BASH_REMATCH[#]}"
}
function unconditionally() { "$#" "$REQUEST_URI"; }
function main() {
local recv=""
local line=""
local REQUEST_METHOD=""
local REQUEST_HTTP_VERSION=""
chk_conf_file
[[ ${UID} = 0 ]] && warn "It is not recommended to run bashttpd as root."
# Request-Line HTTP RFC 2616 $5.1
read -r line || fail_with 400
line=${line%%$'\r'}
recv "${line}"
read -r REQUEST_METHOD REQUEST_URI REQUEST_HTTP_VERSION <<< "${line}"
[ -n "${REQUEST_METHOD}" ] && [ -n "${REQUEST_URI}" ] && \
[ -n "${REQUEST_HTTP_VERSION}" ] || fail_with 400
# Only GET is supported at this time
[ "${REQUEST_METHOD}" = "GET" ] || fail_with 405
while IFS= read -r line; do
line=${line%%$'\r'}
recv "${line}"
# If we've reached the end of the headers, break.
[ -z "${line}" ] && break
REQUEST_HEADERS+=("${line}")
done
}
if [[ ! -z "{$1}" ]] && [ "${1}" = "-s" ]; then
socat TCP4-LISTEN:${LISTEN_PORT},fork EXEC:"${0}"
else
main
source "${BASHTTPD_CONF}"
fail_with 500
fi
LOL, a super lame hack, but at least curl and firefox accepts it:
while true ; do (dd if=/dev/zero count=10000;echo -e "HTTP/1.1\n\n $(date)") | nc -l 1500 ; done
You better replace it soon with something proper!
Ah yes, my nc were not exactly the same as yours, it did not like the -p option.
If you're using Apline Linux, the BusyBox netcat is slightly different:
while true; do nc -l -p 8080 -e sh -c 'echo -e "HTTP/1.1 200 OK\n\n$(date)"'; done
And another way using printf:
while true; do nc -l -p 8080 -e sh -c "printf 'HTTP/1.1 200 OK\n\n%s' \"$(date)\""; done
while true; do (echo -e 'HTTP/1.1 200 OK\r\nConnection: close\r\n';) | timeout 1 nc -lp 8080 ; done
Closes connection after 1 sec, so curl doesn't hang on it.
Type in nc -h and see if You have -e option available. If yes, You can create a script, for example:
script.sh
echo -e "HTTP/1.1 200 OK\n\n $(date)"
and run it like this:
while true ; do nc -l -p 1500 -e script.sh; done
Note that -e option needs to be enabled at compilation to be available.
I think the problem that all the solution listed doesn't work, is intrinsic in the nature of http service, the every request established is with a different client and the response need to be processed in a different context, every request must fork a new instance of response...
The current solution I think is the -e of netcat but I don't know why doesn't work... maybe is my nc version that I test on openwrt...
with socat it works....
I try this https://github.com/avleen/bashttpd
and it works, but I must run the shell script with this command.
socat tcp-l:80,reuseaddr,fork EXEC:bashttpd &
The socat and netcat samples on github doesn't works for me, but the socat that I used works.
Actually, the best way to close gracefully the connection is to send the Content-Length header like following. Client (like curl will close the connection after receiving the data.
DATA="Date: $(date)";
LENGTH=$(echo $DATA | wc -c);
echo -e "HTTP/1.1 200 OK\nContent-Length: ${LENGTH}\n\n${DATA}" | nc -l -p 8000;
On OSX you can use :
while true; do echo -e "HTTP/1.1 200 OK\n\n $(date)" | nc -l localhost 1500 ; done

Resources