Пишем свой веб-сервер на Perl под Android
Сегодня мы с Вами напишем свой собственный веб-сервер на Perl. Что, говорите что этим уже никого не удивишь? А если я скажу, что запустим мы его под Android?
С помощью этого сервера Вы сможете легко обеспечить доступ всех желающих к определённым файлам карты памяти Вашего телефона.
Например, Вы пришли к друзьям в гости и хотите показать новые фотографии с телефона, но кабеля под рукой нет, а смотреть фотографии с телефона не удобно. В таком случае можно будет с компьютера обратиться к веб-серверу и он отдаст нам фотографии.
Ещё, таким же способом можно показывать макет сайта заказчику.
Всех, кому это кажется интересным, проку под кат.
При написании любого скрипта под Android краеугольным камнем является Android Scripting Environment (ASE). Это система от разработчиков Android, с помощью которой можно исполнять скрипты на нескольких языках ( Python, Perl, JRuby, Lua, BeanShell, JavaScript, Tcl и shell ) прямо на телефоне. Скрипты ASE, посредством различных API, позволяют совершать звонки, отправлять текстовых сообщений, сканировать штрих-коды, преобразовать текст в речь и т.д.
Сначала ставим ASE и Perl.

Запускаем test.pl чтобы проверить что всё работает.

Теперь приступим к написанию кода:
use warnings;
use strict;
use Socket;
use IO::Socket;
# Конфигурация сервера
my $port = 3000;
my $root = '/mnt/sdcard';
# Создаём сокет
my $server = new IO::Socket::INET(
Proto => 'tcp',
LocalPort => $port,
Listen => SOMAXCONN,
Reuse => 1
) or die 'Unable to create server socket!' ;
print "Server available at http://localhost:$port/\n";
# Ожидаем подключение
while (my $client = $server->accept()) {
$client->autoflush(1);
# Разбираем запрос
my ($method, $uri, $html_version) = split /\s/, <$client>;
# Отсекаем GET-параметры
$uri =~ s/\?.*$//;
# unescape
$uri =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
# Запрашиваемый файл
my $path = "$root$uri";
if (-e $path) { # Есть что отдать
if (-f $path) { # Это файл
if (-r $path) { # Есть права на чтение
# По расширению определяем content-type
my ($ext) = $uri =~ /\.(\w+)$/;
my $type = mime_type($ext);
print $client http_headers('200 OK', $type);
# Отдаём файл
open FILE, "<$path";
while ( read FILE, my $buffer, 1024 ) { print $client $buffer }
close FILE;
} else { # Нет прав на чтение
# Отдаём ошибку 403
print $client http_headers('403 Forbidden');
print $client '<html><head><title>403 Forbidden</title></head><body>403 Forbidden (Запрещено)</body></html>';
}
} else { # Это каталог
if (-r $path) { # Есть права на чтение, отдаём список фалов
print $client http_headers('200 OK');
print $client <<HTML;
<html>
<head>
<title>Файлы каталога $uri</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>
<link href="/perlonandroid/css/style.css" type="text/css" rel="stylesheet" media="screen" />
<link href="/perlonandroid/css/lightbox.css" type="text/css" rel="stylesheet" media="screen" />
<script src="/perlonandroid/js/jquery.js" type="text/javascript"></script>
<script src="/perlonandroid/js/lightbox.js" type="text/javascript"></script>
<script src="/perlonandroid/js/script.js" type="text/javascript"></script>
</head>
<body>
<div id="head">
<div>Perl On Android</div>
<div class="clear"></div>
</div>
HTML
# Кастомный алгоритм сортировки. Каталоги выше файлов
sub mysort {
if (-d "$path$a") {
-d "$path$b" ? lc$a cmp lc$b : -1
} else {
-d "$path$b" ? 1 : lc$a cmp lc$b
}
}
# Печатаем список файлов
opendir DIR, "$path/";
foreach my $file (sort mysort readdir DIR) {
next if $file =~ /^\.$/;
my $lightbox = '';
if (-d "$path$file") {
$file .= '/';
} else {
my ($ext) = $file =~ /\.(\w+)$/;
$lightbox = "rel=\"image\" title=\"$file\"" if mime_type($ext) =~ /^image/;
}
print $client "<a href=\"$file\" class=\"link\" $lightbox>$file</a>";
}
closedir DIR;
print $client '</body></html>';
} else { # Нет прав на чтение
# Отдаём ошибку 403
print $client http_headers('403 Forbidden');
print $client '<html><head><title>403 Forbidden</title></head><body>403 Forbidden (Запрещено)</body></html>';
}
}
} else { # Файл не найден
# Отдаём ошибку 404
print $client http_headers('404 Not Found');
print $client '<html><head><title>404 Not Found</title></head><body>404 Not Found (Не найдено)</body></html>'
}
# Закрываем поток
close $client;
# Выводим сообщение
print "$method $uri\n";
}
# Формируем HTTP заголовок
sub http_headers {
my ($status, $type) = @_;
$type ||= 'text/html; charset=UTF-8';
return <<RESPONSE;
HTTP/1.0 $status
Server: PerlOnAndroid
Content-type: $type
Connection: close
RESPONSE
}
# get MIME types
sub mime_type {
my ($ext) = @_;
return '' unless defined $ext;
return {
'html' => 'text/html',
'htm' => 'text/html',
'shtml' => 'text/html',
'css' => 'text/css',
'xml' => 'text/xml',
'rss' => 'text/xml',
'gif' => 'image/gif',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'js' => 'application/x-javascript',
'atom' => 'application/atom+xml',
'mml' => 'text/mathml',
'txt' => 'text/plain',
'jad' => 'text/vnd.sun.j2me.app-descriptor',
'wml' => 'text/vnd.wap.wml',
'htc' => 'text/x-component',
'png' => 'image/png',
'tif' => 'image/tiff',
'tiff' => 'image/tiff',
'wbmp' => 'image/vnd.wap.wbmp',
'ico' => 'image/x-icon',
'jng' => 'image/x-jng',
'bmp' => 'image/x-ms-bmp',
'jar' => 'application/java-archive',
'war' => 'application/java-archive',
'ear' => 'application/java-archive',
'hqx' => 'application/mac-binhex40',
'doc' => 'application/msword',
'pdf' => 'application/pdf',
'ps' => 'application/postscript',
'eps' => 'application/postscript',
'ai' => 'application/postscript',
'rtf' => 'application/rtf',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'wmlc' => 'application/vnd.wap.wmlc',
'xhtml' => 'application/vnd.wap.xhtml+xml',
'cco' => 'application/x-cocoa',
'jnlp' => 'application/x-java-jnlp-file',
'run' => 'application/x-makeself',
'pl' => 'application/x-perl',
'pm' => 'application/x-perl',
'prc' => 'application/x-pilot',
'pdb' => 'application/x-pilot',
'rar' => 'application/x-rar-compressed',
'rpm' => 'application/x-redhat-package-manager',
'sea' => 'application/x-sea',
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'tcl' => 'application/x-tcl',
'tk' => 'application/x-tcl',
'der' => 'application/x-x509-ca-cert',
'pem' => 'application/x-x509-ca-cert',
'crt' => 'application/x-x509-ca-cert',
'xpi' => 'application/x-xpinstall',
'zip' => 'application/zip',
'bin' => 'application/octet-stream',
'exe' => 'application/octet-stream',
'dll' => 'application/octet-stream',
'deb' => 'application/octet-stream',
'dmg' => 'application/octet-stream',
'eot' => 'application/octet-stream',
'iso' => 'application/octet-stream',
'img' => 'application/octet-stream',
'msi' => 'application/octet-stream',
'msp' => 'application/octet-stream',
'msm' => 'application/octet-stream',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'kar' => 'audio/midi',
'mp3' => 'audio/mpeg',
'ra' => 'audio/x-realaudio',
'3gpp' => 'video/3gpp',
'3gp' => 'video/3gpp',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mov' => 'video/quicktime',
'flv' => 'video/x-flv',
'mng' => 'video/x-mng',
'asx' => 'video/x-ms-asf',
'asf' => 'video/x-ms-asf',
'wmv' => 'video/x-ms-wmv',
'avi' => 'video/x-msvideo',
}->{lc $ext} || '';
};
Самое трудное позади! Теперь сохраняем скрипт в каталог /mnt/sdcard/sl4a/scripts. Также в корне карты памяти нужно расположить этот каталог со статикой (css, js). Теперь можно набрать в браузере телефона http://localhost:3000/ и увидеть что-то похожее на это:

Теперь все желающие могут получить доступ к файлам катры памяти вашего телефона. Из изображений, с помощью плагина lightbox, автоматически формируется галерея.

На этом всё, спасибо за внимание. Надеюсь Вы получили удовольствие от прочтения.