[HN Gopher] Sherver: Bash lightweight web server
       ___________________________________________________________________
        
       Sherver: Bash lightweight web server
        
       Author : turrini
       Score  : 147 points
       Date   : 2021-12-22 11:56 UTC (11 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | zoomablemind wrote:
       | Just an idea, along the lines of a single binary webserver: one
       | may use Fossil SCM [1] to easily spawn a webserver ('fossil
       | server', or even 'fossil ui'), no need for root (port 8080 by
       | default).
       | 
       | The pages could be served as part of a project (all version-
       | controlled), or as documentation pages, or as wiki. Styling is
       | somewhat limited, but could still be done creatively.
       | 
       | Additionally, a static website or even a whole application (PHP
       | or the likes) could be rolled out via /ext, the 'extension' path.
       | 
       | [1]: https://www.fossil-scm.org
        
       | Brian_K_White wrote:
       | Ah, no. Bold claim unrealized. Immediately I see many unnecessary
       | forks and externals to do things that bash actually can do
       | itself.
        
       | IceWreck wrote:
       | If this is pure bash then so is
       | 
       | ``` caddy file-server --browse --listen :80 ```
        
       | [deleted]
        
       | rascul wrote:
       | This seems like an odd usage of cat and command substitution to
       | me:                   HEAD_TEMPLATE=$(cat <<EOF
       | <title>Sherver example</title>             <meta
       | name="description" content="Sherver example">         EOF
       | )
       | 
       | https://github.com/remileduc/sherver/blob/master/scripts/ind...
        
         | rascul wrote:
         | I believe the pure bash way of assigning a multi line string to
         | a variable, without dealing with quoting, might be as follows:
         | IFS='' read -r -d '' HEAD_TEMPLATE <<'EOF'
         | <title>Sherver example</title>             <meta
         | name="description" content="Sherver example">         EOF
         | 
         | https://stackoverflow.com/questions/1167746/how-to-assign-a-...
        
         | junon wrote:
         | It achieves multiline block text in bash, which is otherwise a
         | PITA to reason about. It can keep certain scripts cleaner,
         | especially since `cat` is often inlined, without process
         | spawning.
        
           | steerablesafe wrote:
           | > especially since `cat` is often inlined, without process
           | spawning.
           | 
           | That would be surprising. How would that work? `cat` is
           | definitely not a bash built-in.
        
             | junon wrote:
             | Cat is not a specified built-in but many implementations
             | _do_ build it in to speed up scripts that use it quite
             | heavily.
             | 
             | Other examples include `which`, `time`, `echo`, etc.
             | They're usually promoted to built-ins when it's clear
             | that's the intended command to run, but there are still
             | analogs on the PATH if need-be - I'm not sure exactly how
             | bash would detect a non-standard executable there to know
             | when it's unsafe to promote to a built-in, though.
             | 
             | For example with echo:                   $ bash -c 'echo
             | hi'         hi                  $ strace bash -c 'echo hi'
             | | grep 'exec'         execve("/usr/bin/bash", ["bash",
             | "-c", "echo hi"], 0x7fffe264aad0 /* 24 vars */) = 0
             | $ strace bash -c '$(which echo) hi' | grep 'exec'
             | execve("/usr/bin/bash", ["bash", "-c", "$(which echo) hi"],
             | 0x7fffe36c6cd0 /* 24 vars */) = 0
             | execve("/usr/bin/echo", ["/usr/bin/echo", "hi"],
             | 0x7fffe2e16ff0 /* 25 vars */) = 0
             | 
             | In fact for me, `cat` _always_ gets inlined, even if I call
             | it with its full path or with `which cat`.
             | 
             | There _are_ cases where the built-in and the actual
             | executable differ - the most prominent case being `time`.
             | The built-in does not have the command line arguments to
             | print out things like pre-empted scheduling (forced context
             | swiches).                   $ time --help         --help:
             | command not found                  $ /usr/bin/time --help
             | Usage: /usr/bin/time [-apvV] [-f format] [-o file]
             | [--append] [--verbose]               [--portability]
             | [--format=format] [--output=file] [--version]
             | [--quiet] [--help] command [arg...]
        
               | steerablesafe wrote:
               | "echo" and "time" are explicitly bash built-ins, "which"
               | and "cat" are not, and you will always get a process
               | spawned there.
               | 
               | Your second strace is misleading, you didn't trace child
               | processes.                   $ strace bash -c '$(which
               | echo) hi' 2>&1 | grep 'exec'         execve("/bin/bash",
               | ["bash", "-c", "$(which echo) hi"], 0x7ffddfcdaee0 /* 59
               | vars */) = 0         execve("/bin/echo", ["/bin/echo",
               | "hi"], 0x55eb565cb0b0 /* 59 vars */) = 0
               | $ strace -f bash -c '$(which echo) hi' 2>&1 | grep 'exec'
               | execve("/bin/bash", ["bash", "-c", "$(which echo) hi"],
               | 0x7ffd09b3e228 /* 59 vars */) = 0         [pid 81493]
               | execve("/usr/bin/which", ["which", "echo"],
               | 0x5560d172e0b0 /* 59 vars */) = 0
               | execve("/bin/echo", ["/bin/echo", "hi"], 0x5560d172e0b0
               | /* 59 vars */) = 0
        
               | rascul wrote:
               | You had:                   $ strace bash -c 'echo hi' |
               | grep 'exec'         execve("/usr/bin/bash", ["bash",
               | "-c", "echo hi"], 0x7fffe264aad0 /\* 24 vars */) = 0
               | 
               | Something seems off. strace(1) prints the trace to stderr
               | not stdout, so you should need |& not | to catch it with
               | grep. At least, that's how it works here.
               | 
               | Also, cat does not appear to be inlined for me:
               | rascul@smarts:~> strace bash -c 'cat /etc/os-release' |&
               | grep 'exec'         execve("/usr/bin/bash", ["bash",
               | "-c", "cat /etc/os-release"], 0x7ffe7b5334e0 /* 88 vars
               | */) = 0         execve("/usr/bin/cat", ["cat", "/etc/os-
               | release"], 0x562c447076f0 /* 88 vars */) = 0*
        
               | junon wrote:
               | Yeah good catch, I forgot it since I didn't copy 1:1 the
               | command line into vim when I formatted it for HN. I use
               | fish + powerline so the command line doesn't copy
               | correctly. In fish, it's `&|`, which would look weird
               | anyway. I just forgot to add the `&` after the `|`.
               | 
               | The output was copied 1:1 though.
               | 
               | The inlining is an implementation detail and I've seen it
               | happen differently across different platforms.
        
               | rascul wrote:
               | > The inlining is an implementation detail and I've seen
               | it happen differently across different platforms.
               | 
               | Do you have more information on this? I would be
               | interested to read more about it. As far as I was aware
               | (and my knowledge is certainly not exhaustive), the only
               | thing kinda related is as noted in the bash man page:
               | 
               | > The command substitution $(cat file) can be replaced by
               | the equivalent but faster $(< file).
        
               | junon wrote:
               | I don't, sorry. I just know it's something some
               | implementations opt-in to (especially drop-in
               | replacements to bash). It's behavior I've observed quite
               | a bit and have leveraged it in some cases to improve the
               | performance of some critical bash scripts over the years.
               | I'd also be interested in some prose about it.
        
               | prussian wrote:
               | Are you sure?                   strace -ff -e execve
               | /bin/bash -c 'x="$(cat <<< "test")"'         ... snip
               | [pid 311214] execve("/usr/bin/cat", ["cat"],
               | 0x56280da76fc0 /* 3 vars */) = 0
        
               | junon wrote:
               | Yep, I'm sure. It's not a guarantee (nor is it specified,
               | to my knowledge) that the inlining happens. You also used
               | a subshell there, which might make a difference.
        
               | BeefWellington wrote:
               | The original poster is using a subshell. Your example
               | uses echo, which is both a built-in _AND_ an external
               | program.
               | 
               | They are not comparable. I've never seen a shell inline
               | cat, probably because it would make no sense given there
               | are POSIX builtins to do exactly what cat does.
        
               | junon wrote:
               | It of course makes sense since `cat` is widely used for
               | the purpose of the original code, just not in a subshell.
               | It's used often to output blocks of text to stdout/err
               | for multiline output, namely --help text.
               | 
               | strace doesn't lie here, and I've seen it substitute cat
               | in trivial cases quite often.
        
       | vkazanov wrote:
       | One those things that is s scarily beautiful
        
       | pkrumins wrote:
       | Also `php -S` is very handy.
        
       | MisterTea wrote:
       | Plan 9's rc-httpd is quite useful, supports cgi and serves many
       | plan 9 web sites, many of which run werc http://werc.cat-v.org/
       | 
       | It can also run on Unix via plan 9 port (p9p) or derivatives.
       | 
       | https://github.com/9front/9front-test/blob/master/rc/bin/rc-...
        
       | notRobot wrote:
       | See also:
       | 
       | darkttpd: When you need a web server in a hurry.
       | 
       | https://github.com/emikulic/darkhttpd
        
       | montroser wrote:
       | Fantastic. For a more sinatra-inspired take, see also
       | https://github.com/guigo2k/ganesh
        
       | medv wrote:
       | It is a socat server, not bash. Basically a bash script calling
       | socat.
       | 
       | Definitely not "pure".
        
         | tyingq wrote:
         | Bash can't do listen sockets, so I don't think a pure bash web
         | server is possible. You can make an "pure" gawk web server,
         | though it works terribly
         | https://gist.github.com/willurd/5720255#gistcomment-3143007
        
         | dang wrote:
         | Ok, we've impured the title above.
        
         | [deleted]
        
         | prussian wrote:
         | Someone beat me to it. When I think of _pure_ bash, I don 't
         | really think of invoking anything that isn't a builtin or a
         | grammatical feature of bash. If socat is fair-play, almost
         | anything you can spawn on a shell should count then.
         | 
         | edit: on top of that, I already found a perl script utility in
         | the repo:
         | https://github.com/remileduc/sherver/blob/master/scripts/uti...
        
           | fragbait65 wrote:
           | I think I'll just write something similar and wrap Apache
           | with a shell scrip, claim that it's a web server written in
           | bash, post it here and see what reaction I get, just for
           | shits and giggles.
        
             | prussian wrote:
             | Here: https://github.com/remileduc/sherver/issues/1 that
             | way you can have HTTP/1.1 support even with arbitrary
             | output lengths. I was planning on sending this to the
             | linked repo so the owner doesn't need to use HTTP/1.0.
             | 
             | EDIT: changed link to the github issue I created with
             | inlined utility.
        
           | bluetomcat wrote:
           | Jokingly, "sudo service apache2 start" may also be considered
           | pure Bash :-)
        
             | fragbait65 wrote:
             | Exactly!
        
         | junon wrote:
         | ~~Right. "Pure" bash would mean they somehow got sockets
         | working in the scripting language itself, which is not possible
         | unless you're on a system with `/dev/tcp` (which isn't many
         | these days).~~
         | 
         | I am wrong! See response.
        
           | rzzzt wrote:
           | /dev/tcp is a Bash construct (mentioned as "pseudo-device
           | file" in the manual):
           | https://tldp.org/LDP/abs/html/devref1.html
        
             | junon wrote:
             | Oh huh, TIL it's only a Bash thing. I stand corrected - I
             | thought it was a Linux thing that was removed for security
             | reasons.
             | 
             | Thanks for correcting me :)
        
         | loeg wrote:
         | There's always https://github.com/cemeyer/httpd.sh .
        
       | throwaway984393 wrote:
       | They reinvented CGI scripts but didn't actually implement CGI.
       | Could have implicitly supported a lot of apps by default. There's
       | some pretty decent CGI libraries for Bash, too.
       | 
       | Personally I use busybox's httpd (which does support CGI) for a
       | small portable web server.
        
       | laumars wrote:
       | > I didn't want to install and configure Apache or nGinx. In
       | fact, I didn't want any configuration.
       | 
       | If you ignore all of the manual installation instructions and any
       | dev time you took to create this, then sure.
       | 
       | I'm not going to argue that the author should have used something
       | else instead though. It's a personal project used purely on an
       | internet system of his house. For those kinds it projects
       | "because I could" is as good a reason as any. I just think more
       | authors should be honest and say "I just did it because it seemed
       | fun at the time"
        
         | tjoff wrote:
         | Manual instructions and dev time doesn't necessarily pollute
         | the state of the machine though and are harder to replicate,
         | which is how I would interpret it.
        
           | laumars wrote:
           | Not having your application packaged up for quick removal
           | strikes me as more polluting. And I don't really see how an
           | independent solution with almost no documentation and zero
           | configuration management support (eg Ansible) is easier to
           | replicate than a battle tested industry standard with
           | widespread support.
           | 
           | Don't get me wrong, I have nothing against building things
           | for fun. But the technical arguments made for why this is
           | preferable over an established solution doesn't stack up.
        
             | tjoff wrote:
             | I do.
             | 
             | Just as I would use this (would look into it more first
             | though), the netcat trick mentioned here or python3 -m
             | http.server over ever considering installing Ansible and/or
             | nginx/apache.
             | 
             | Totally different use case though.
        
               | laumars wrote:
               | If I was rolling my own and concerned about polluting,
               | I'd favour a language like Rust or Go because I then
               | don't need to install the Python runtime, have random
               | undocumented dependencies for the bash etc.
               | 
               | Or I'd use Docker.
               | 
               | But as said before, there's no wrong answer here since
               | it's just a hobby project running on a HTPC.
        
               | tjoff wrote:
               | With that I agree, I would only use python if it was
               | already installed, and the assumption with bash was that
               | everything needed was already installed.
        
       | moondev wrote:
       | go run github.com/patrickhener/goshs@latest
        
       | _wldu wrote:
       | Here's my small webserver:                   #!/bin/bash
       | while : ; do cat conference.txt | nc -l 80; done
       | 
       | Here's the story behind it:
       | 
       | https://www.go350.com/posts/finding-a-hacked-server/
        
         | tessierashpool wrote:
         | for a more complete solution, check out Bash On Balls, a Rails
         | clone written in bash:
         | 
         | https://github.com/jneen/balls
        
         | laputan_machine wrote:
         | I could read stories like this forever, they are educational
         | and engrossing, thanks for sharing
        
         | [deleted]
        
         | Tepix wrote:
         | I liked the story and the impromptu web server!
         | 
         | Two comments:
         | 
         | - using "ping -a" creates an audible ping so you can find the
         | server if you're by yourself without having to look at the
         | screen.
         | 
         | - No need for cat:                   while :; do nc -l 80 <
         | conference.txt; done
        
           | vincnetas wrote:
           | TIL. thanx for -a.
        
           | _wldu wrote:
           | Interesting. I don't hear anything when I use the -a flag.
        
             | rzzzt wrote:
             | Windows Terminal shows a "visual bell" in the tab bar
             | whenever BEL is emitted (even though I have it set to
             | "audible" in advanced settings of the profile, hmm).
             | 
             | Edit: Uhm, right. I have also silenced all system sounds in
             | Windows.
        
             | thanatos519 wrote:
             | Let's check with "reveal codes":                   $ ping
             | -a 127.0.0.1 | less              PING 127.0.0.1 (127.0.0.1)
             | 56(84) bytes of data.              64 bytes from 127.0.0.1:
             | icmp_seq=1 ttl=64 time=0.029 ms^G              64 bytes
             | from 127.0.0.1: icmp_seq=2 ttl=64 time=0.023 ms^G
             | 
             | Looks like you need a terminal that has a BEL sound.
        
             | vesinisa wrote:
             | It just echoes a Bell character to your terminal. Make sure
             | your terminal emulator supports it, and is configured
             | properly: https://en.wikipedia.org/wiki/Bell_character
        
           | dormento wrote:
           | Whoa, nifty.
           | 
           | Why does firefox shows the "chose an application to open this
           | link" when you access the http://localhost link, but shows
           | the content correctly when you access the 127.0.0.1/0.0.0.0
           | ip-based version?
        
             | hericium wrote:
             | Browser expects to see some HTTP headers before the
             | content. Adding HTTP version with response code, content
             | type declaration and empty line dividing headers and
             | content should do the trick:                   HTTP/1.1 200
             | Content-Type: text/plan;              1st line of content
        
         | [deleted]
        
       | montroser wrote:
       | For the occasional service where you are essentially just
       | wrapping an existing binary, a bash server behind uwsgi can be
       | totally sensible.
        
       | adamddev1 wrote:
       | Very cool. Was hoping to see Sean Connery in the README.
        
       | BossingAround wrote:
       | > I didn't want to install and configure Apache or nGinx. In
       | fact, I didn't want any configuration.
       | 
       | Personally, in that case, I'd use:
       | 
       |  _python3 -m http.server_
        
         | rascul wrote:
         | There are many similar options. I think that awhile ago there
         | may have been a list of one liner web servers posted to HN.
         | Either way, I've been using python's for years and it's just
         | muscle memory now.
        
           | em500 wrote:
           | Big list of http static servers:
           | 
           | https://gist.github.com/willurd/5720255
        
         | nickjj wrote:
         | That's my goto solution for a simple server if I'm testing
         | something locally but it has issues dealing with concurrent
         | requests in its simple form.
         | 
         | For example I ran a simple hello world test service on
         | Kubernetes once using Python's http.server.
         | 
         | Just having 3 Kubernetes health checks happen on regular
         | intervals would routinely cause the server to respond with
         | errors. I think what ends up happening is if you have 2
         | requests coming in within a short enough period of time one of
         | them will fail. You have to use a more complicated threaded
         | http server if you want a zero dependency solution.
        
         | adamddev1 wrote:
         | `npm install -g live-server`
         | 
         | Also works great (but a bit bulkier sure).
        
       | remote wrote:
       | Kudos for scripting a great hack in BASH.
       | 
       | Since the title reads "pure bash" however I was expecting to see
       | an implementation using builtin bash features such as /dev/tcp.
       | 
       | Have you considered solving this problem using BASH builtin
       | support for TCP?
        
         | rascul wrote:
         | I recall that bash can't create a server socket, because bash
         | doesn't bind().
        
       ___________________________________________________________________
       (page generated 2021-12-22 23:01 UTC)