четверг, 19 августа 2010 г.

Свой Web-PDF принтер за 10 минут



Как потратить совсем немного времени и сделать что-нибудь простое и оригинальное, поражающее своей глобальностью — но абсолютно бесполезное? Очень просто. Давайте сделаем свой принтер.

Нам понадобится (кроме головы и рук) только работающий web-сервер с поддержкой cgi-bin, к которому у нас есть доступ по FTP. Есть такой? Поехали!


Создаем в папочке cgi-bin скрипт printer. Содержание скрипта очень простое:


#! /usr/bin/perl

use strict;

if (!defined($ENV{'CONTENT_TYPE'}) || $ENV{'CONTENT_TYPE'} ne "application/ipp") {
    print "Content-Type: text/html\n\n";
    print ":-)";
    exit;
}

# $d - входные данные в виде строки
# $l - длина строки входных данных
# $i - текущая позиция разбора данных
# %a - разобранные атрибуты

my ($d, $l, $i, %a) = ("", 0, 0);
binmode STDIN;
$l += read(STDIN, $d, 4096, $l) while (!eof(STDIN));

parseRequest(\$d, \$l, \$i, \%a);

my $o = ""
 . substr($d, 0, 2) # version
 . chr(0x00) . chr(0x00) # status
 . substr($d, 4, 4) # request
 . chr(0x01) # attributes
 . stringAttribute(0x47, "attributes-charset", "utf-8")
 . stringAttribute(0x48, "attributes-natural-language", "en-us")
 . chr(0x04) # attributes
 . stringAttribute(0x42, "printer-name", "PDF")
 . chr(0x03) # end
 . chr(0x0a)
;

print "Content-Type: text/html\n";
print "Content-Length: " . length($o) . "\n";
print "\n";
print $o;

if (defined($a{'-status'}) && $a{'-status'} == 0x02 && $i < $l) {
    my @t = localtime;
    my $output = sprintf("../pdf/%04d%02d%02d-%02d%02d%02d.pdf", $t[5] + 1900, $t[4], $t[3], $t[2], $t[1], $t[0]);
    if (open(P, "|-", "gs", "-q", "-dBATCH", "-dNOPAUSE", "-dSAFER", "-sDEVICE=pdfwrite", "-sOutputFile=$output", "-")) {
        binmode P;
        print P substr($d, $i);
        close P;
    }
}

sub parseRequest {
    my ($d, $l, $i, $a) = @_;
    return if $$i >= $$l - 2;
    $$a{'-version'} = (ord(substr($$d, $$i, 1)) << 8) + ord(substr($$d, $$i + 1, 1)); $$i += 2;
    return if $$i >= $$l - 2;
    $$a{'-status'} = (ord(substr($$d, $$i, 1)) << 8) + ord(substr($$d, $$i + 1, 1)); $$i += 2;
    return if $$i >= $$l - 4;
    $$a{'-request'} = parseInt(substr($$d, $$i, 4)); $$i += 4;
    return if $$i >= $$l - 1;
    my $what = ord(substr($$d, $$i, 1)); $$i ++;
    return parseAttributes($d, $l, $i, $a) if ($what == 0x01);
}

sub parseAttributes {
    my ($d, $l, $i, $a) = @_;
    while ($$i < $$l) {
        my $what = ord(substr($$d, $$i, 1)); $$i ++;
        return if ($what == 0x03);
        return parseAttributes($d, $l, $i, $a) if ($what == 0x02);
        return parseAttributes($d, $l, $i, $a) if ($what == 0x04);
        return if $$i >= $$l - 2;
        my $key_len = (ord(substr($$d, $$i, 1)) << 8) + ord(substr($$d, $$i + 1, 1)); $$i += 2;
        return if $$i >= $$l - $key_len;
        my $key = substr($$d, $$i, $key_len); $$i += $key_len;
        return if $$i >= $$l - 2;
        my $val_len = (ord(substr($$d, $$i, 1)) << 8) + ord(substr($$d, $$i + 1, 1)); $$i += 2;
        return if $$i >= $$l - $val_len;
        my $val = substr($$d, $$i, $val_len); $$i += $val_len;
        $$a{$key} = $val;
    }
}

sub parseInt {
    my $v = shift;
    my $l = length($v);
    my $r = 0;
    for (my $i = $l; $i > 0; $i --) {
        $r += ( (1 << (($i - 1) * 8)) * ord(substr($v, $l - $i, 1)) );
    }
    $r -= 4294967296 if ($r >= 2147483648);
    return $r;
}

sub stringLength {
    my $s = shift;
    my $l = length($s);
    my $i1 = $l & 0xFF;
    $l = ($l - $i1) >> 8;
    my $i2 = $l & 0xFF;
    return chr($i2) . chr($i1);
}

sub stringAttribute {
    my ($type, $key, $val) = @_;
    return chr($type) . stringLength($key) . $key . stringLength($val) . $val;
}


Как видите, никаких внешних зависимостей от модулей Perl, и ничего лишнего. Фактически, нужны только Perl и программа gs, которые есть практически везде.

Скрипт когда-то был основан на PHP::Print::IPP. Но, так как на большинстве серверов выполнять внешние программы из PHP-скриптов запрещено, то пришлось переписать на Perl. Скрипт реализует самую-самую базовую функциональность IPP-сервера.

Далее, даем права на исполнение скрипта (755, или rwxr-xr-x). Смотрим в браузере: http://www.site.ru/cgi-bin/printer. Работает? Хорошо.

Еще создадим папку pdf в корне сайта и установим права на запись к этой папке (777, или rwxrwxrwx).

Теперь добавляем принтер в Windows:
  • Установка принтера
  • Сетевой принтер или принтер, подключенный к другому компьютеру
  • Подключиться к принтеру в Интернете, в домашней сети или в интрасети: http://www.site.ru/cgi-bin/printer
  • Изготовитель: Generic, модель: MS Publisher Imagesetter
И печатаем пробную страницу. В папочке pdf на сервере появляется наша пробная страница.

Аналогичным образом можно напечатать много чего. Документы, картинки… Все, что угодно.

Осталось сделать три замечания.

1. Теоретически, скрипт может не работать, по самым разным причинам. Основные принципы отладки Perl-скриптов оставлю на самостоятельное изучение.
2. Желательно переименовать скрипт printer в какой-нибудь более хитрый, чтобы все подряд не печатали на Вашем принтере. Через .htaccess закрыть доступ к CGI-скрипту достаточно сложно, сделать авторизацию внутри Perl-скрипта тоже. А то можно было бы...
3. Принтер нормально работает под Windows Vista и Linux. Установка, в целом, не такая уж и сложная. Но вот нужно ли это?..



Источник: Хабрахабр - Ненормальное программирование
Оригинальная страница: Свой Web-PDF принтер за 10 минут

Комментариев нет:

Отправить комментарий