Home > SHELL, UNIX, tips, Программирование > HTTP сервер в одну строку: версия 2.0

HTTP сервер в одну строку: версия 2.0

August 30th, 2007

Идея с HTTP сервером на bash мне не дает покоя.

Вернуться к ней меня побудила.. попытка воспользоваться старой версией: получалсь не очень :)

Поэтому решил довести его до ума. Что-то дописал, что-то выкинул, полчилось следующее:
:;while [ $? -eq 0 ];do nc -vlp 8080 -c'(r=read;e=echo;$r a b c;z=$r;while [ ${#z} -gt 2 ];do $r z;done;f=`$e $b|sed 's/[^a-z0-9_.-]//gi'`;h="HTTP/1.0";o="$h 200 OK\r\n";c="Content";if [ -z $f ];then($e $o;ls|(while $r n;do if [ -f "$n" ]; then $e "<a href=\"/$n\">`ls -gh $n`</a><br>";fi;done););elif [ -f $f ];then $e "$o$c-Type: `file -ib $f`\n$c-Length: `stat -c%s $f`";$e;cat $f;else $e -e "$h 404 Not Found\n\n404\n";fi)';done

Теперь по URL http://ваш_ip:8080/ можно получить доступ ко всем файлам, находящимся в текущим каталоге. Очевидных и прямолинейных способов сменить каталог нет. Как и раньше, протестировано и работает под Linux, bash 3.2.13, и с хоббитовским netcat v1.10 с поддержкой опции -с (запустите netcat -h и посмотрите. Как минимум в Ubuntu, Debian и Fedora Core такая опция есть).

Теперь строка занимает 434 байт, что на 212 больше, чем прошлая версия. И мне кажется, что я с толком потратил эти байты: новый сервер обрабатывает ошибку 404, кроме того, теперь он работает без задержки, которая раньше требовалась для того, чтобы была возможность его остановить с помощью Ctrl-C. Для успешных запросов сервер отдает нормальные заголовки, с размером файла и даже с его MIME-типом. Например:

dainichi@fujitsu:~/backup$ echo "GET /6.1-RELEASE-i386-disc1.iso HTTP/1.1" | nc localhost 8080 | head -n3
HTTP/1.x 200 OK
Content-Type: application/x-iso9660
Content-Length: 529784832

Для удобства можно сохранить его в файл и написать в .bash_profile примерно следующее:alias share='sh ~/bash_httpd.sh'

Да, были попытки его еще уменьшить, но они не увенчались успехом. Есть такой вариант, но он даже больше:
w=while;d=done;e=echo;r=read;echo "true; $w [ @? -eq 0 ];do nc -vlp 8080 -c'($r a b c;f={$e @b|sed 's/[^a-z0-9_.-]//gi'{;h=}HTTP/1.x};o=}@h 200 OK#};c=}Content};if [ -z @f ];then($e @o;ls|(while $r n;do $e }@n
};done));elif [ -f @f ];then $e }@o@c-Type: {file -ib @f{#@c-Length: {stat -c%s @f{#};cat @f;else $e }@h 404 Not Found##404#};fi)';done"|tr "@{}#" '$`"
'|sh
Оставил его, потому что выглядит страшно, детей пугать – самое то :)

Если у вас получится сэкономить еще пару байтов, то милости просим в комментаторскую :)

UPD: 03.09.2007: Код обновлен, в него вошли исправления, которые сделал тов. jetxee, за которые ему большое спасибо.

UPD: 03.09.2007: Я удивлен тому факту, что эта заметка попала в топ news2, спасибо dik’у за то, что добавил ее. Я рад, что она многим показалась интересной.

UPD: 07.09.2007: Товарищ Ed очень здорово прооптимизировал код. Вот, что у него вышло:
while :;do nc -p8080 -vnlc'r=read;e="echo -e";$r a b c;while [ -n "`$e $a|tr -d "\r\n"`" ];do $r a;done;f=`$e $b|sed s/.//`;h="HTTP/1.0";z="404 Not Found\n";[ -z $f ]&&(ls|while $r n;do [ -f $n ]&&$e "$n";done)||([ -f $f ]&&($e "$h 200 OK\r\nContent-Type: `file -ib $f`\n";cat $f)||$e "$h $z\n$z")';done

Работоспособность проверялась автором на таблетке Nokia N800, а у меня в Kubuntu 7.04 что-то не заработало. Сегодня вечером буду разбираться :)

p.s. Мне теперь стыдно из-за того, что сам не догадался использовать && и || вместо if..then. :(

p.p.s. stay tuned!

UPD 18.08.2008: О, написали про это безобразие на Хабре, назвали меня извращенцем. Хулиганьё!! :)

SHELL, UNIX, tips, Программирование , , , , ,

  1. August 30th, 2007 at 03:11 | #1

    Занятная идея. Порадовал :)

    Работает, если запрос слать телнетом, но не работает, если запрашивать браузером (Epiphany) или wget. Пишется ««127.0.0.1» разорвал соединение».

    У меня возникло подозрение, что это из-за того, что скрипт начинает отвечать, не дождавшись конца запроса (а должен читать заголовки запроса до пустой строки, сейчас читается только первая строка). Я это пофиксил и wget заработал.

    Также, наверное, при выдаче каталога хорошо бы отдавать Content-Type. В частности, без верного Content-Type wget сохраняет пустой файл.

    И поставить HTTP/1.0. Или поставить HTTP/1.1 и добавить Connection: close. (HTTP/1.1 applications that do not support persistent connections MUST include the “close” connection option in every message. –RFC2616)

    Ещё есть подозрение, что символы перевода строк где-то «защищаются» bash. А именно во время вывода echo. Приходят литералы «\n» вместо кодов CRLF. Да, положено, чтобы шли именно CRLF. И заголовок ответа также должен заканчиваться пустой строкой.

    В общем, вот мой вариант (предлагаю оптимизировать по длине):

    true; while [ $? -eq 0 ];do nc -vlp 8080 -c'(r=read;e=echo;$r a b c;E=NOTYET;while [ ${#E} -gt 0 ];do $r E;E=`$e $E|tr -d "\r\n"`;done;f=`$e $b|sed 's/[^a-z0-9_.-]//gi'`;h="HTTP/1.0";o="$h 200 OK\r\n";c="Content";if [ -z $f ];then($e -e $o;ls|(while $r n;do if test -f ${n}; then $e "$n";fi;done););elif [ -f $f ];then $e -e "$o$c-Type: `file -ib $f`\n$c-Length: `stat -c%s $f`\n";cat $f;else $e -e "$h 404 Not Found\n\n404\n";fi)';done

  2. Alexey Sveshnikov
    August 30th, 2007 at 10:34 | #2

    Блин, как это же я раньше не додумался до -f фильтрации вывода! :)

    А по поводу Connection: close – я так писал в одной из первых версий, но потом, когда добился работоспособности, стал убирать все лишнее. Без этого заголовка все работало, вот я его и удалил. Но я тестировал только под оперой и мозиллой, и еще вручную смотрел на заголовки. Та же история с чтением заголовков – просто не обращал на это внимания, работает и все.

    А по поводу HTTP 1.0 ты совершенно прав, 1.1 тут ни к чему.

    Что касается твоего варианта – на этот раз странности у меня :) Список файлов не в виде ссылок, и при получении файла в вывод попала опция -e у echo. Сегодня уделю этому время и попробую совместить обе версии.

  3. August 30th, 2007 at 15:13 | #3

    Если я правильно понимаю, в HTTP/1.0 «Connection: close» быть и не должно. Это только для HTTP/1.1. Так что всё нормально.

    По поводу странностей моего варианта, спасибо, что отметил: похоже, при посте комментария попортилось… Копипаст из моего комментария у меня тоже не показывает ссылки, а копипаст из моего блога — работает. wdiff показывает, что пропали бэкслэши и аттрибут href в ссылке. Вот ещё раз, надеюсь, не испортится:

    true; while [ $? -eq 0 ];do nc -vlp 8080 -c’(r=read;e=echo;$r a b c;E=NOTYET;while [ ${#E} -gt 0 ];do $r E;E=`$e $E|tr -d “\r\n”`;done;f=`$e $b|sed ’s/[^a-z0-9_.-]//gi’`;h=”HTTP/1.0″;o=”$h 200 OK\r\n”;c=”Content”;if [ -z $f ];then($e -e $o;ls|(while $r n;do if test -f ${n}; then $e “<a href=\”/$n\”>$n</a><br>”;fi;done););elif [ -f $f ];then $e -e “$o$c-Type: `file -ib $f`\n$c-Length: `stat -c%s $f`\n”;cat $f;else $e -e “$h 404 Not Found\n\n404\n”;fi)’;done

    Главной проблемой было, что клиент начинал ждать ответа только после завершения запроса, а скрипт к тому времени уже всё выстреливал и разрывал соединение. А остальное уже мелочи.

    Думаю, это достаточно кроссплатформенный вебсервер. У меня Debian testing, netcat 1.10-33, bash 3.1dfsg-8 (3.1.17).

  4. August 30th, 2007 at 15:21 | #4

    Опять не вышло, кавычки испортились… В общем, вот здесь в одну строчку: http://jetxee.googlepages.com/bashhttpd-2.1.txt

  5. Alexey Sveshnikov
    August 30th, 2007 at 15:45 | #5

    asveshnikov@fe08a023fb1cc27a:~$ echo -ne "GET / HTTP/1.1\r\n\r\n" | nc -vv localhost 8080 | head
    localhost [127.0.0.1] 8080 (webcache) open
    -e HTTP/1.0 200 OK

    :
    ...

    У нас все-таки среда немного разная..

  6. Alexey Sveshnikov
    August 30th, 2007 at 18:58 | #6

    jetxee, ага. То, что ты выложил в отдельном файле работает. Почему ‘-e’ иногда воспринимается как опция, а иногда как аргумент мне не понятно до сих пор.

    Еще подсократил и выложил вместо первоначальной версии

  7. September 2nd, 2007 at 22:53 | #7

    У меня не заработало. Пишет:
    nc: invalid option — c
    nc -h for help

    Это на CentOS release 4.5.

  8. render77
    September 3rd, 2007 at 01:59 | #8

    жжоте!

  9. September 3rd, 2007 at 09:42 | #9

    А на каких системах еще это наверное заработает?

  10. Alexey Sveshnikov
    September 3rd, 2007 at 10:49 | #10

    Бомж, DenisO:

    Для работы, как я только что понял, нужен не просто netcat, а netcat патчем sh-c. У меня kubuntu, я посмотрел на исходники пакета и обнаружил, что на один маленький netcat приходится кроме этого, еще штук 20 патчей.

    Вобщем, попробуйте для начала написать netcat -h – если там есть опция -c, значит, будет работать, если нет – то нет.

    (обновил пост)

  11. Alexey Sveshnikov
    September 3rd, 2007 at 11:00 | #11

    Да, пока я рассматривал исходники пакета обнаружил в дистрибутиве netcat несколько весьма любопытных примеров. Среди них есть и HTTP-сервер, правда он какой-то громоздкий.

  12. anonym
    September 3rd, 2007 at 11:27 | #12

    maladec! tak derjat’ ty BASH HACKER

  13. September 3rd, 2007 at 16:24 | #13

    Да, вот так: на News2.ru засветился :) Поздравления!

  14. Alexey Sveshnikov
    September 3rd, 2007 at 16:32 | #14

    jetxee: да, это приятно :)
    Мотивирует на то, чтобы писать дальше и не только для себя.

  15. September 4th, 2007 at 12:27 | #15

    Супер! Пошёл дальше пиарить :)

  16. September 4th, 2007 at 13:53 | #16

    У меня Ваша версия выдала просто пустой скрин, без списка файлов. Вот.

  17. Alexey Sveshnikov
    September 4th, 2007 at 14:56 | #17

    RiZN, а что за система?

  18. September 4th, 2007 at 21:34 | #18

    Все круто, только дайте, пожалуйста, патч, что неткат -с умел!

  19. Alexey Sveshnikov
    September 6th, 2007 at 10:17 | #19

    Cybpsy, вот здесь есть: http://packages.ubuntu.com/feisty/net/netcat

  20. sunTechnic
    September 6th, 2007 at 12:05 | #20

    а у меня строка еще меньше:
    /usr/local/etc/rc.d/apache.sh start

    громкое название, а всю работку делает netcat

  21. Alexey Sveshnikov
    September 6th, 2007 at 14:50 | #21

    sunTechnic, не согласен, netcat здесь обеспечивает только интерфейс с сетью, а поддержка /dev/tcp в баше, имхо, встречается гораздо реже, чем netcat с ‘-c’

  22. September 7th, 2007 at 12:43 | #22

    Люди возраждают старые забавы :)

    http://public.planetmirror.com/pub/pshttpd/

  23. September 7th, 2007 at 12:50 | #23

    Кстати, у меня, на сервере какая-то бородатая федора (3-я, кажется), netcat нет, зато есть /dev/tcp/ в bash :)

  24. Alexey Sveshnikov
    September 7th, 2007 at 14:50 | #24

    Bolk, по ссылке – красотища!

    /dev/tcp это хорошо, но насколько я понимаю, слушать сокеты с помощью этого механизма нельзя.

  25. September 7th, 2007 at 16:16 | #25

    Ну, как ж нельзя:

    exec 8<>/dev/tcp/www.ru/80
    echo -e "GET /eng/index.html HTTP/1.0\n" >&8
    cat < &8

  26. Alexey Sveshnikov
    September 7th, 2007 at 16:46 | #26

    Я имел ввиду открывать сокет и ждать соединения к нему клиентов :)

    Т.е. без конструкции nc -l -p 8080 не обойтись.

  27. September 7th, 2007 at 17:05 | #27

    Да, слушать нельзя, точно :)

  28. September 7th, 2007 at 17:16 | #28

    Кстати, есть же какой-то способ в Linux при открытии сокета перенаправлять данные через pipe в другую программу. inetd, что ли, так умеет…

  29. Alexey Sveshnikov
    September 7th, 2007 at 17:24 | #29

    Bolk, да, можно так сделать. Если убрать из моего скрипта цикл с неткатом, а оставить только аргумент опции -с, то оставшаяся часть должна работать.

    Но это заморочка, мне было бы лень логиниться под рутом и править inetd.conf для того, чтобы посмотреть, как работает какая-то безделушка.

  30. September 11th, 2007 at 13:52 | #30

    Скрипт опробован на IT Т800 и успешно работает :) Рекомендован в качестве простого расшарочного средства. За что и выражаю благодарности :)

  31. Alexey Sveshnikov
    September 11th, 2007 at 14:20 | #31

    dik, обалдеть :)
    Спасибо :)

  32. Ed
    September 16th, 2007 at 15:10 | #32

    Идея супер! Спасибо!
    Вот мой вариант. 328 байт. Работает на N800. Я выбросил Content-Length, потому как на девайсе нет stat. Наверное его нужно вернуть, но работает и без него. Надеюсь, что даже если это где-то не заработает, идеи по сокращению на пару байт здесь можно почерпнуть :)

    while :;do nc -p8080 -vnlc'r=read;e="echo -e";$r a b c;while [ -n "`$e $a|tr -d "\r\n"`" ];do $r a;done;f=`$e $b|sed s/.//`;h="HTTP/1.0";z="404 Not Found\n";[ -z $f ]&&(ls|while $r n;do [ -f $n ]&&$e "$n";done)||([ -f $f ]&&($e "$h 200 OK\r\nContent-Type: `file -ib $f`\n";cat $f)||$e "$h $z\n$z")';done

  33. Alexey Sveshnikov
    September 17th, 2007 at 11:30 | #33

    Ed, у меня пока не заработала твоя версия, но по коду вижу, что твои исправления очень и очень дельные.
    Получилось намного красивее.

    Спасибо :)

    (обновил пост)

  34. Ed
    September 17th, 2007 at 13:56 | #34

    Он искорежился просто блогом.
    Вот здесь более человечно должно быть:
    http://internet-tablet.com/software/byistraya-rassharka-faylov-s-tabletki-cherez-http-server-v-odnu-stroku-na-bash/#comment-842

  35. Ed
    September 17th, 2007 at 13:58 | #35

    А, ты поправил, вижу. Как его, кстати, постить сюда правильно. pre и code тэги использовать?
    Что именно не заработало? Я проверял wget-ом и 2-мя браузерами – firefox-ом и оперой.

  36. Alexey Sveshnikov
    September 17th, 2007 at 15:52 | #36

    Ed, я немного подправил форму комментирования, см. подсказку.

    Не заработало под оперой – ничего не отобразилось. Вечером посмотрю что именно не так.

  37. Ed
    September 17th, 2007 at 17:40 | #37

    Посмотрел я еще раз на код повнимательнее – там все-таки не хватает нужных частей, при посте сожрались. В частности понятно почему оно у тебя не заработало.
    Ох уж эти мне блоги :)
    Ладно, пойдем другим путем.
    Вот ссылка на скрипт: http://www.bartosh.org/files/shared
    Просто вытащи wget-ом, он ничего не поломает.
    Размер должен быть ровно 328 байт

  38. Alexey Sveshnikov
    September 17th, 2007 at 18:32 | #38

    Размер тот же, но не заработало :)

    При вставке в <code> ничего не бьется, я многократно проверял.

  39. Ed
    September 17th, 2007 at 19:36 | #39

    Странно, я вставлял именно используя code.
    Пробую еще раз:
    while :;do nc -p8080 -vnlc'r=read;e="echo -e";$r a b c;while [ -n "`$e $a|tr -d "\r\n"`" ];do $r a;done;f=`$e $b|sed s/.//`;h="HTTP/1.0";z="404 Not Found\n";[ -z $f ]&&(ls|while $r n;do [ -f $n ]&&$e "$n";done)||([ -f $f ]&&($e "$h 200 OK\r\nContent-Type: `file -ib $f`\n";cat $f)||$e "$h $z\n$z")';done

  40. Ed
    September 17th, 2007 at 19:37 | #40

    Неа, не работает.

  41. Alexey Sveshnikov
    September 18th, 2007 at 15:24 | #41

    Ed, потому что ты заменяешь & на &amp, а этого делать не надо :)
    Просто пиши как есть!

  42. Ed
    September 18th, 2007 at 21:25 | #42

    Я просто вставил скрипт между тэгами code, ничего не менял. И раньше тоже не менял. Это все он, блог :)

  43. ls
    September 27th, 2007 at 10:52 | #43

    Хочу заставить работать во frebsd

  44. October 2nd, 2007 at 11:49 | #44

    Есть у меня похожая задача, из-за чего и начал разбираться в этом оч интересном скрипте…

    Дистр у меня Slackware 12. netcat там без -с… пошел скачал пропатченый, запустил nc -h и вижу: ” -c shell commands as `-e’; use /bin/sh to exec [dangerous!!]” получается можно и не патченый использовать только нада вместо “-с” написать “-e /bin/sh $path_to_the_script”?

  45. Alexey Sveshnikov
    October 2nd, 2007 at 12:09 | #45

    GByte, у меня под рукой из недебиановских дистрибутивов отказались только старенький Gentoo и RHEL4, и там и там, почему-то, у нетката не оказалось даже опции -e (хотя в мане она упомянута).
    Так что проверить вашу гипотезу не могу, сорри.

  46. October 2nd, 2007 at 12:29 | #46

    Попробую свою поделку написать…
    Заодно проверю работает ли опция -е..

    но по вашему коду могу только сказать что вы его сильно компрессировали ;)

  47. zapalyt
    December 17th, 2007 at 22:50 | #47

    izvenite no nam nado sdelat test
    vi ved ne bydete protiv admini

    такой трогательный спам пришел, что я даже решил его пропустить – asv

  48. April 10th, 2008 at 11:04 | #48

    простите я развернул ваш код, для правильной работы добавил пару исправлений:
    cat ~/bin/bash.sh

    #! /bin/bash
    :;
    while [ $? -eq 0 ] ; do
    nc -vlp 8080 -c '(
    read a b c
    z=read
    while [ ${#z} -gt 2 ] ; do
    read z
    done
    f=`echo $b|sed 's/[^a-z0-9_.-]//gi'`
    h="HTTP/1.0";o="$h 200 OK\r\n";c="Content"
    if [ -z $f ] ; then
    (echo $o
    ls -1|(while read n ; do
    if [ -f "$n" ] ; then
    echo "`ls -gh "$n"`"
    fi
    done);
    );
    elif [ -f $f ] ; then
    echo "$o$c-Type: `file -ib $f`\n$c-Length: `stat -c%s $f`"
    echo
    cat $f
    else echo -e "$h 404 Not Found\n\n404\n"
    fi)'
    done

  49. Alexey Sveshnikov
    April 10th, 2008 at 11:50 | #49

    Исправления по-вашему, это опция -1 к ls?

  1. September 3rd, 2007 at 23:08 | #1
  2. September 4th, 2007 at 12:59 | #2