HTTP сервер в одну строку: версия 2.0
Идея с 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;(for n in *;do if [ -f "$n" ]; then $e “`ls -gh \”$n\”`
“;fi;done););elif [ -f "$f" ];then $e “$o$c-Type: `file -ib \”$f\”`\n$c-Length: `stat -c%s \”$f\”`”;$e;$e $f>&2;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'
Если у вас получится сэкономить еще пару байтов, то милости просим в комментаторскую :)
UPD: Код обновлен, в него вошли исправления, которые сделал тов. jetxee, за которые ему большое спасибо.
UPD: Заметка попала в топ news2, спасибо dik’у за то, что добавил ее. Я рад, что она многим показалась интересной.
UPD: Товарищ 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 что-то не заработало. Сегодня вечером буду разбираться :). Мне теперь стыдно из-за того, что сам не догадался использовать && и || вместо if..then. :(
UPD: О, на хабре написали.
Для владельцев Ubuntu 10.10: Теперь в дистрибутиве по умолчанию стоит другой вариант netcat’а. Вот так можно вернуть все как было, это абсолютно безвредное для системы действие:
sudo apt-get install netcat-traditional
sudo update-alternatives –set nc /bin/nc.traditional
P.S. Ссылка на скрипт в plain text: http://sveshnikov.ru/httpd.sh, он же, в более удобночитаемом виде: http://sveshnikov.ru/httpd.sh.formatted


Занятная идея. Порадовал :)
Работает, если запрос слать телнетом, но не работает, если запрашивать браузером (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Блин, как это же я раньше не додумался до -f фильтрации вывода! :)
А по поводу Connection: close – я так писал в одной из первых версий, но потом, когда добился работоспособности, стал убирать все лишнее. Без этого заголовка все работало, вот я его и удалил. Но я тестировал только под оперой и мозиллой, и еще вручную смотрел на заголовки. Та же история с чтением заголовков – просто не обращал на это внимания, работает и все.
А по поводу HTTP 1.0 ты совершенно прав, 1.1 тут ни к чему.
Что касается твоего варианта – на этот раз странности у меня :) Список файлов не в виде ссылок, и при получении файла в вывод попала опция -e у echo. Сегодня уделю этому время и попробую совместить обе версии.
Если я правильно понимаю, в 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).
Опять не вышло, кавычки испортились… В общем, вот здесь в одну строчку: http://jetxee.googlepages.com/bashhttpd-2.1.txt
asveshnikov@fe08a023fb1cc27a:~$ echo -ne "GET / HTTP/1.1\r\n\r\n" | nc -vv localhost 8080 | headlocalhost [127.0.0.1] 8080 (webcache) open
-e HTTP/1.0 200 OK
:
...
У нас все-таки среда немного разная..
jetxee, ага. То, что ты выложил в отдельном файле работает. Почему ‘-e’ иногда воспринимается как опция, а иногда как аргумент мне не понятно до сих пор.
Еще подсократил и выложил вместо первоначальной версии
У меня не заработало. Пишет:
nc: invalid option — c
nc -h for help
Это на CentOS release 4.5.
жжоте!
А на каких системах еще это наверное заработает?
Бомж, DenisO:
Для работы, как я только что понял, нужен не просто netcat, а netcat патчем sh-c. У меня kubuntu, я посмотрел на исходники пакета и обнаружил, что на один маленький netcat приходится кроме этого, еще штук 20 патчей.
Вобщем, попробуйте для начала написать netcat -h – если там есть опция -c, значит, будет работать, если нет – то нет.
(обновил пост)
Да, пока я рассматривал исходники пакета обнаружил в дистрибутиве netcat несколько весьма любопытных примеров. Среди них есть и HTTP-сервер, правда он какой-то громоздкий.
maladec! tak derjat’ ty BASH HACKER
Да, вот так: на News2.ru засветился :) Поздравления!
jetxee: да, это приятно :)
Мотивирует на то, чтобы писать дальше и не только для себя.
Супер! Пошёл дальше пиарить :)
У меня Ваша версия выдала просто пустой скрин, без списка файлов. Вот.
RiZN, а что за система?
Все круто, только дайте, пожалуйста, патч, что неткат -с умел!
Cybpsy, вот здесь есть: http://packages.ubuntu.com/feisty/net/netcat
а у меня строка еще меньше:
/usr/local/etc/rc.d/apache.sh start
громкое название, а всю работку делает netcat
sunTechnic, не согласен, netcat здесь обеспечивает только интерфейс с сетью, а поддержка /dev/tcp в баше, имхо, встречается гораздо реже, чем netcat с ‘-c’
Люди возраждают старые забавы :)
http://public.planetmirror.com/pub/pshttpd/
Кстати, у меня, на сервере какая-то бородатая федора (3-я, кажется), netcat нет, зато есть /dev/tcp/ в bash :)
Bolk, по ссылке – красотища!
/dev/tcp это хорошо, но насколько я понимаю, слушать сокеты с помощью этого механизма нельзя.
Ну, как ж нельзя:
exec 8<>/dev/tcp/www.ru/80echo -e "GET /eng/index.html HTTP/1.0\n" >&8
cat < &8
Я имел ввиду открывать сокет и ждать соединения к нему клиентов :)
Т.е. без конструкции nc -l -p 8080 не обойтись.
Да, слушать нельзя, точно :)
Кстати, есть же какой-то способ в Linux при открытии сокета перенаправлять данные через pipe в другую программу. inetd, что ли, так умеет…
Bolk, да, можно так сделать. Если убрать из моего скрипта цикл с неткатом, а оставить только аргумент опции -с, то оставшаяся часть должна работать.
Но это заморочка, мне было бы лень логиниться под рутом и править inetd.conf для того, чтобы посмотреть, как работает какая-то безделушка.
Скрипт опробован на IT Т800 и успешно работает :) Рекомендован в качестве простого расшарочного средства. За что и выражаю благодарности :)
dik, обалдеть :)
Спасибо :)
Идея супер! Спасибо!
Вот мой вариант. 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")';doneEd, у меня пока не заработала твоя версия, но по коду вижу, что твои исправления очень и очень дельные.
Получилось намного красивее.
Спасибо :)
(обновил пост)
Он искорежился просто блогом.
Вот здесь более человечно должно быть:
http://internet-tablet.com/software/byistraya-rassharka-faylov-s-tabletki-cherez-http-server-v-odnu-stroku-na-bash/#comment-842
А, ты поправил, вижу. Как его, кстати, постить сюда правильно. pre и code тэги использовать?
Что именно не заработало? Я проверял wget-ом и 2-мя браузерами – firefox-ом и оперой.
Ed, я немного подправил форму комментирования, см. подсказку.
Не заработало под оперой – ничего не отобразилось. Вечером посмотрю что именно не так.
Посмотрел я еще раз на код повнимательнее – там все-таки не хватает нужных частей, при посте сожрались. В частности понятно почему оно у тебя не заработало.
Ох уж эти мне блоги :)
Ладно, пойдем другим путем.
Вот ссылка на скрипт: http://www.bartosh.org/files/shared
Просто вытащи wget-ом, он ничего не поломает.
Размер должен быть ровно 328 байт
Размер тот же, но не заработало :)
При вставке в <code> ничего не бьется, я многократно проверял.
Странно, я вставлял именно используя 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Неа, не работает.
Ed, потому что ты заменяешь & на &, а этого делать не надо :)
Просто пиши как есть!
Я просто вставил скрипт между тэгами code, ничего не менял. И раньше тоже не менял. Это все он, блог :)
Хочу заставить работать во frebsd
Есть у меня похожая задача, из-за чего и начал разбираться в этом оч интересном скрипте…
Дистр у меня Slackware 12. netcat там без -с… пошел скачал пропатченый, запустил nc -h и вижу: ” -c shell commands as `-e’; use /bin/sh to exec [dangerous!!]” получается можно и не патченый использовать только нада вместо “-с” написать “-e /bin/sh $path_to_the_script”?
GByte, у меня под рукой из недебиановских дистрибутивов отказались только старенький Gentoo и RHEL4, и там и там, почему-то, у нетката не оказалось даже опции -e (хотя в мане она упомянута).
Так что проверить вашу гипотезу не могу, сорри.
Попробую свою поделку написать…
Заодно проверю работает ли опция -е..
но по вашему коду могу только сказать что вы его сильно компрессировали ;)
izvenite no nam nado sdelat test
vi ved ne bydete protiv admini
такой трогательный спам пришел, что я даже решил его пропустить – asv
простите я развернул ваш код, для правильной работы добавил пару исправлений:
cat ~/bin/bash.sh
Исправления по-вашему, это опция -1 к ls?