программирование

Экономия трафика для мобильных

Идея такая: экраны мобильных относительно небольшие. Самое здоровое, что я встречал — 5″ на моём предыдущем телефоне (HTC X7500). Это значит, что нет нужды показывать на этих дисплеях столь же большие картинки, как и на ноутах и десктопах.

Хочу со временем попробовать такую схему: картинка заливается ко мне на сайт и в «Яндекс.Фотки». С сайта показывается изображения моего обычного формата для компьютеров и ноутов, с «Яндекс.Фоток» (а там из каждой фотографии готовится семь превьюшек разного размера) — для сотовых в подходящем для экрана размере.

Единственное, что я пока не продумал — как автоматически подставлять разные изображения. Из CSS нельзя управлять атрибутом SRC картинки, а то можно было бы воспользовать media queries; можно было бы использовать background-image, но это сломает мои галереи картинок и изменит порядок загрузки фото.

Хотелось бы использовать SVG, но WebKit на «Андроидах» и iOS (в iOS поддерживается с 2.1) не понимает SVG (в SVG можно сунуть ссылки на несколько картинок и выбрать нужную через те же media queries).

Можно было бы сделать подмену со стороны сервера, но для этого нужно знать разрешение на экране пользователя. В этом мог бы помочь сервис «Яндекс.Детектор». Но мне неизвестно насколько быстро пополняется база новыми моделями, какой у неё охват старых и что делать, если сервис в какой-то момент не ответит.

Остаётся JavaScript, что грустно. Надо подумать ещё.

32 комментария

Браузер в Windows Phone 7.0

У нас в компании появились первые смартфоны на Windows Phone 7.0. Естественно, очень интересно посмотреть что поддерживает тамошний браузер.

Пишу по мотивам одного из отзывов внутри компании.

HTML5Test — 15 баллов, ни одного бонусного (нет canvas, svg, video/audio, тегов HTML5, Geo API, SSE/WebSocket, localStorage), тест modernizr показывает отсутствие border-radius, rgba, box-shadow, opacity, gradients, transforms, transitions, flexbox, border-image, text-shadow и ещё многого другого.

Нет touch events, отсутствует Array.forEach, Object.defineProperty и ещё по мелочи.

Код юзер-агента выглядит вот так:

Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; HTC; 7 Mozart T8698)

17 комментариев

Tanks JS1K

Кстати о JavaScript. Может быть кто-то из читатей помнит, под Новый год мы с LO развлекались тем, что уменьшали мою игру про танк, воюющий с ракетами, чтобы она занимала как можно меньше места.

Не так давно мне почему-то вспомнилась эта игрушка и я посмотрел нельзя ли там что-то ещё уменьшить, свежим взглядом так сказать. Оказалось можно. У меня была старая идея переделать координатную адресацию, но я её забросил, лениво. Зато придумал кое-что новенькое, что пригодится не только в этой игрушке.

Мне очень хотелось сократить названия методов канваса, очень уж они длинные — putImageData, createImageData и так далее. Для игры, занимающей (сейчас) 713 байт это расточительно. На эту тему в голову идея и пришла.

Идея заключается в том, чтобы пройтись по списку методов и присвоить объекту их же, но под другими, короткими именами. Самый простой способ, до которого я додумался — это взять пятую букву названия метода (правда, кажется a[i] для строк не везде будет работать, вариант с charAt занимает на 7 байт больше).

Получился такой код:

for(i in a)a[i[V=4]]=a[i]

Переменная «V» со значением «четыре» используется дальше как счётчик оставшихся жизней, в переменной «a» ссылка на объект контекста канваса. В итоге, если мне надо вызывать метод «a.putImageData», я могу вызвать его как «a.m», вместо «a.createImageData» можно вызывать «a.t» и так далее (отчёт с нуля начинается).

Чем больше таких вызовов, тем больше экономия. Единственное, свойства (например «fillStyle») так сократить не получится.

2 комментария

«Go»: два новых метода в gd-go: SmoothArc и SmoothEllipse

Добавил в свой проект gd-go (биндинг GD для Go) два новых метода: SmoothArc и SmoothEllipse. Оба рисуют соответствующие залитые фигуры с антиалиасингом. Используется алгоритм Ульриха Мерендорфа, который я портировал из PHP.

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

JS Linux

JS/Linux (27.15КиБ)

Все уже видели Linux на JavaScript, который работает в браузере (нужен Google Chrome 11 или FireFox 4)?

В отличие от моего JUnix, который являлся эмулятором, написанным на JavaScript, это эмулятор процессора x86 с кое-какой периферией, на котором запущен Linux, в котором есть компилятор (tcc, Tiny C Compiler v 0.9.25), в котором можно скомпилировать программу на Си в полноценный ELF и запустить!

Можно сравнить насколько быстрее стали браузеры за прошедшие годы, JUnix я писал в 1999-2001 гг, тогда он еле ворочался, а тут — реализуется железо и это всё быстро работает. Фантастика!

Добавлено позднее: есть более ранний эмулятор Unix V6 на PDP-11, другого автора Шмидта, внутри есть ассемблер (команда as) и Фортран (fc).

Добавлено ещё позднее: 20 мая в JS/Linux была добавлена поддержка «Оперы» 11.11 и IE9.

3 комментария

StackBlur в go-gd

Портировал алгоритм Stack Blur с языка Processing на Google Go, теперь в моей библиотеке go-gd появился метод StackBlur.

Методы Smooth и GaussianBlur, которые я портировал из форка PHP-GD мне не нравились, не давали удобоваримого размытия, StackBlur куда лучше.

Вот небольшое описание этого алгоритма от автора, Марио Клингемана:

This is a compromise between Gaussian Blur and Box blur. It creates much better looking blurs than Box Blur, but is 7x faster than my Gaussian Blur implementation.

I called it Stack Blur because this describes best how this filter works internally: it creates a kind of moving stack of colors whilst scanning through the image. Thereby it just has to add one new block of color to the right side of the stack and remove the leftmost color. The remaining colors on the topmost layer of the stack are either added on or reduced by one, depending on if they are on the right or on the left side of the stack.

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

«Go»: количество го-программ

«Го-программы» (goroutines) — это термин языка «Go», это функции, которые выполняются параллельно, что-то вроде местных потоков. Я пока ещё почти не умею с ними работать, только учусь. Одна из особенностей, на которую я сразу наткнулся, то что в языке есть специальный вызов «GOMAXPROCS» (модуль runtime), который указывает сколько процессоров будет задействованно, чтобы запускать го-программы.

По-умолчанию, «Go» использует только один процессор, вот цитата:

It should be smarter and one day it will be smarter, but until it is if you want CPU parallelism you must tell the run-time how many goroutines you want executing code simultaneously. There are two related ways to do this. Either run your job with environment variable GOMAXPROCS set to the number of cores to use (default 1); or import the runtime package and call runtime.GOMAXPROCS(NCPU).

В документации есть множество мест, где говорится, что сейчас планировщик го-программ работает очень плохо, его будут улучшать и однажды «GOMAXPROCS» уйдёт в историю; а пока приходится выставлять это значение вручную.

Возможно я ошибаюсь, но мне кажется очевидным, что значение этой директивы должно быть уж никак не меньше числа процессоров. Чтобы автоматически определять количество процессоров и указывать верное, по моему мнению, значение, я написал следующий код. Возможно ещё кому-нибудь пригодится.

package main

import ("syscall"; "runtime")

func main() {
    if n, _ := syscall.SysctlUint32("hw.ncpu"); n > 0 {
        runtime.GOMAXPROCS(int(n))
    }
}

20 комментариев

Развитие биндинга графической библиотеки «GD» для «Go»

Несколько лет назад авторы языка PHP форкнули библиотеку GD, биндинг к которой для языка Go я вчера написал, и стали развивать собственную версию. В язык PHP входит именно форк.

Исходная библиотека беднее, например, там нет вызова ImageConvolution и всего того, что с ним можно делать (размытие, выделение краёв и так далее), а мне хочется, чтобы в моём биндинге они были. Можно, конечно, выдрать GD из РHP, но я хочу написать более-менее универсальное решение, а во все пакеты на всех платформах входит именно оригинальная библиотека.

Почесав репу, я потратил пару часов, переписал часть PHP-библиотеки GD на «Go» и воткнул её в свой биндинг. Теперь в go-gd есть вызовы GrayScale, Negate, Brightness, Contrast, Color, Convolution, GaussianBlur, EdgeDetectQuick и Emboss.

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

Биндинг графической библиотеки «GD» для «Go»

Написал за часть ночи и часть утра биндинг к графической библиотеке «GD»: go-gd.

Дело в том, что несмотря на существование модуля «image», в «Go» нет почти ничего для работы с графикой, всё что умеет язык — читать и писать JPEG и PNG (скоро появится поддержка GIF и TIFF), копировать области, получать значение цвета в заданных координатах, рисовать прямоугольники и точки. Это всё.

А мне, по условиям программисткой игры, в которую я ввязался, нужно уметь работать с графикой куда более разнообразно, например, изменять размеры изображения (resize image). Единственная сторонняя библиотека для этого, которая существует — «scale», сильно портит картинку.

Биндинги писать на «Гоу» чрезвычайно просто, единственное — язык запрещает читать переменную сишную errno и я не нашёл никакого способа получить её значение.

Покрытие «GD» у меня не 100%, есть функции, которые я просто поленился реализовывать. Руководства к пользованию нет, есть небольшой пример использования, makefile для его сборки и к модулю «GD» для PHP, там почти всё совпадает (только у меня функций намного меньше, GD в PHP не тот, что использую я, а форк).

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

Сколько занимает программа на «Go»

Хочу немного развеять тот миф, который я невольно создал — что скомпилированные программы, написанные на языке «Go» занимают очень много. Мой скомпилированный пример с веб-сервером (программа «httphere») занимает почти шесть мегабайт и это не выглядит много для такой программы.

На самом деле, всё не так просто. Простенькая программа на «Go», например, классический «Hello, world!» займёт около 230КБ:

bolk-osx:Hello bolk$ cat <<GO > hello.go
> package main; func main() { println("Hello, world!") }
> GO

bolk-osx:Hello bolk$ 6g hello.go && 6l -o hello hello.6 && du -h hello 
232K	hello

При увеличении исходного кода размер вырастает незначительно, в моей шестимегабайтной программе большую часть занимает веб-сервер (модуль «http»), если его убрать, то скомпилированная программа «похудеет» примерно на 5,5МБ.

Сейчас я пишу программу где уже 414 строк (в httphere — 132) и подключено 11 стандартных модулей, включая модуль для работы с изображениями («image»), скомпилированная версия занимает 1,5 мегабайта. Хотел бы попробовать сжать файл архиватором UPX, но, к сожалению, он не понимает тип запускаемых файлов моей системы (Mach-O 64 бита).

9 комментариев

«Go»: чтение ini-файлов

Мне тут для программы на языке «Go» понадобился простой парсер ini-файлов, который я и написал. Делюсь.

Кстати говоря, я сильно облегчил себе жизнь, когда написал небольшой bash-скрипт, компилирующий и запускающий программы на «Go», вот он:

#!/bin/bash
NAME=$1

if [ -f "$NAME" ]; then
    TMPNAME=`mktemp -t go.XXXXXXX`

    shift
    6g -I. -o $TMPNAME.6 $NAME && 6l -L. -o $TMPNAME $TMPNAME.6 && $TMPNAME $*
    rm -f $TMPNAME.6 $TMPNAME >/dev/null 2>&1
else
    echo "Error: '$NAME' is not found."
fi

Сам модуль выглядит вот так (и называется «ini»):

package ini

import (
    "os"
    "bufio"
    "strings"
)

type inisection map[string]string
type inifile map[string]inisection

func ParseFile(name string) inifile {
    f, err := os.OpenFile(name, os.O_RDONLY, 0666)
    if err != nil {
        return nil
    }

    defer f.Close()

    r := bufio.NewReader(f)

    one := inisection{}             // одна, текущая секция
    ini := inifile   {}             // все секции
    section := ""                   // текущее название секции

    for {
        line, err := r.ReadString('\n')

        if err != nil {
            break
        }

        chunks := strings.Split(strings.TrimRight(line, "\r\n"), "=", 3)
        if len(chunks) == 2 {
            key, value := strings.Trim(chunks[0], "\t "), strings.Trim(chunks[1], "\t ")

            last := len(value) - 1
            if last > 0 && value[0] == '"' && value[last] == '"' {
                value = string(value[1:last])
            }

            one[key] = string(value)
        } else {
            last := len(chunks[0]) - 1

            if chunks[0][0] == '[' && chunks[0][last] == ']' {
                ini[section] = one
                one = inisection{}
                section = string(chunks[0][1:last])
            }
        }
    }

    // «поджимаем хвост», поскольку предыдущая секция попадает в map
    // только когда появляется следующая, последнюю нужно перекладывать «руками»

    if len(one) > 0 {
        ini[section] = one
    }

    return ini
}

Модули пишутся очень просто — достаточно объявить как он будет называться (в директиве «package») и можно его подключать через «import». В «Go» модуль экспортирует всё, что начинается с большой буквы.

Мой модуль экспортирует единственную функцию — ParseFile, принимающую на вход имя файла, на выходе будет ассоциативный массив ассоциативных массивов (map of maps), ключами верхнего уровня является имя секции (если отсутствует, то имя пустое), ключами второго уровня — ключи ini-файла.

12 комментариев

«Go»: обработка ошибок

После первого прочтения документации по языку «Гоу» у меня сложилось неправильное впечатление о конструкции «panic», отчасти из-за её названия. Мне показалось, что «panic» используется для генерирования фатальной, для программы, ошибки, которую лучше не обрабатывать, а прекратить выполнение, во избежание серьёзных проблем.

Моё небольшое исследование показало, что это не так, «panic» — это стандартное средство генерации ошибки (аналог исключений), тогда как «defer» плюс «recover» — стандартный способ сгенерированную ошибку отловить и обработать.

Напомню как это делается. Генерация ошибки осуществляется вызовом «panic», перехват — defer-вызовом функции, в которой при помощи функции recover проверяется был ли defer-вызов произведён из-за вызова «panic» (defer вызывается, когда управление, по любой причине, покидает текущую зону видимости).

Например:

package main

func test() int {
    defer func() {
        if e := recover(); e != nil {
            print("Error\n")
        }

        panic(true)
}

func main() {
   print(test())
}

Ошибки могут быть типизированными: можно вызвать «panic(42)», «panic(„Hello“)», «panic(struct{h, k int}{0, 0})» или, к примеру, «panic(os.NewError(„Cannot open file“))», тип ошибки можно узнать, так же как и её содержимое. Это уже больше похоже на исключения.

Если внимательно посмотреть на мой пример выше, то не совсем ясно следующее: «паника» привёла к выполнению анонимной defer-функции и, фактически, прервала выполнение функции «test». Эта функция была описана, как возвращающая результат типа «int». Что нужно сделать, чтобы в случае вызова «panic», мы смогли вернуть какой-нибудь результат?

Способ есть, давайте разберём его в другом примере. В нём же я покажу как именно определить тип «паники».

package main

import . "os" // содержимое модуля будет доступно без префикса

func poisson(n int) (result string) {
    defer func() {
        switch r := recover(); r.(type) {
            case string:
                result = "Result: " + r.(string) + "\n"
            case Error:
                result = "Error: " + r.(Error).String() + "\n"
        }
    }()

    if n < 0 {
        // NewError и Error используется из модуля «os»
        panic(NewError("Poisson distributions have no value over negative numbers"))
    }

    panic("I dunno")
}

func main() {
    print(poisson(-1)) // Poisson distributions have no value over negative numbers
    print(poisson(+1)) // I dunno
}

У функции «poisson», которая притворяется, что считает распределение Пуассона, возвращаемый параметр имеет имя, он как бы связан с переменной «result». Если мы что-то поместим в эту переменную, то это и будет возвращаемым функцией значением. Поскольку в «Go» есть замыкания, мы можем использовать переменную «result» и внутри анонимной функции в defer-вызове. Всё просто.

Что происодит дальше. Вызов «panic» принимает на вход «interface{}», то есть, фактически, параметр любого типа. Чтобы определить тип этого параметра, используется специальнуя конструкция «переменная.(type)» (которая, к слову, может использоваться только в операторе «switch»).

Упомянутая конструкция может определить какой тип ошибки нам вернулся. Похожая конструкция «переменная.(имя-типа)» („r.(string)“ и „r.(Error)“) называется «type assertion». Она позволяет, в общих чертах, проверить, что в переменной сохранено значение указанного типа и получить это значение.

Полученные значения обрабатываются и сохраняются в переменной «result», которая связана со значением возвращаемым функцией «test», оно и попадает в print функции «main».

Я уже настолько проникся языком, что это решение уже мне не кажется таким странным, как поначалу.

4 комментария

«Go»: указатели и арифметика

Одна из особенностей «Go», о которой пишут в большинстве статей по языку, это наличие в языке указателей, но отсутствие арифметики с ними. Вот, например, цитата из статьи «Go For C++ Programmers»:

Go has pointers but not pointer arithmetic. You cannot use a pointer variable to walk through the bytes of a string.

В самом деле, если попробовать скомпилировать следующий пример, то нас ждёт неудача:

package main
func main() {
    var pointer *[]uint8 = new([]uint8)
    pointer++ // invalid operation: pointer += 1 (mismatched types *[]uint8 and int)
}

Никакими преобразованиями базовых типов не добиться чтобы язык разрешил прибавить хоть что-то к указателю. Я написал «базовых» и это неслучайно. Язык действительно не содержит арифметики с указателями, но в языке есть стандартные модули.

В модуле «unsafe» есть специальный тип «Pointer», в руководстве по использованию которого написано следующее:

1) A pointer value of any type can be converted to a Pointer.
2) A Pointer can be converted to a pointer value of any type.
3) A uintptr can be converted to a Pointer. 4) A Pointer can be converted to a uintptr.

В свою очередь, uintptr встроен в язык и это не указатель, это обычный числовой тип, пусть вас не смущает слово «ptr». Указатель напрямую нельзя преобразовать к типу uintptr или обратно, но можно это сделать через unsafe.Pointer, как видно из цитаты выше.

И вот что получается:

package main

import "fmt"
import "unsafe"

func main() {
    e := [...]uint8{11,22,33} // создаю массив из трёх байт — 1, 2, 3
    p := &e[0] // p — указатель на первый элемент массива (тип *uint8)

    fmt.Println(*p) // выводит «11»

    p = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + 1)) // указатель преобразован в число, прибавляется «1», преобразуем обратно в указатель
    fmt.Println(*p) // выводит «22»

    s := "12345" // строка, содержащая «12345»
    *(*uintptr)(unsafe.Pointer(&s))++ // сдвигаем адрес, на который ссылается «s» 

    fmt.Println(s, len(s)) // «2345», но длина всё равно 5 (!), в последнем байте неотображаемый символ
}

10 комментариев

Послевкусие от «Go»

Должен сказать, что у «Go», как, наверное, у любого нового языка программирования, в изобилии свои, модные, ни на что не похожие тараканы. Небольшой пример приведу перед сном.

У «Go» есть хеши, называются «map», описываются несколько необычно: нужно описать не только тип значений, но и тип ключей. «Go» язык строгий, для него это важно:

package main
import `fmt`

func main() {
    arr := map [string] byte { `a`: 1, `b`: 2}
    fmt.Println(arr)
}

Эта простенькая программка выведет на экран что ожидается: «map[a:1 b:2]», то есть строковое представление нашей переменной типа «map». Давайте представим, что из переменной «arr» нужно удалить какое-то значение по ключу. Что для этого нужно сделать, как вы думаете?

Можете прочитать руководство и попробовать найти нам функции «del», «delete», «remove», «destroy», что угодно, ничего такого вы там не найдёте. Попытка присвоить «nil» ни к чему не приведёт — язык не позволит это сделать. Что делать?

Можно, конечно, пересоздать переменную с новым значениями, но есть способ… э… лучше. Почему я в этом не уверен будет понятно дальше.

Каждый элемент переменной типа «map» содержит специальную магию. Если запросить у элемента не одно, а два значения, то вернутся два — значение элемента и флаг, существует ли он:

val, exists := arr["a"]
fmt.Println(val, exists) // выведет «1 true»

Чтобы удалить значение из хеша надо присвоить второму значению «false»:

arr["a"] = arr["a"], false // a["a"][1] = false не сработает, у него другой смысл
fmt.Println(arr) // выдаёт map[b:2]

val, exists = arr["a"]
fmt.Println(val, exists) // выведет «0 false», значение сброшено и «не существует»

Если запросить у хеша значение с ключом, которого никогда в нём не было, эффект будет тот же — нулевое или эквивалентное ему значение и «false», тут всё логично.

Выбор такого решения для удаления значения мне кажется странным.

На всякий случай, приведу место из официального руководства, где это всё описано, чтобы не возникло ощущение, что я просто его прочитал невнимательно:

Similarly, if an assignment to a map has the special form a[x] = v, ok and boolean ok has the value false, the entry for key x is deleted from the map; if ok is true, the construct acts like a regular assignment to an element of the map.

8 комментариев

«Go» паникует

Попробую рассказать ещё об одной вещи, которая показалась мне в «Go» странной, дополню предыдущий пост.

Иногда, скомпилированная программа, написанная на «Go» «паникует»:

bolk-osx:Sample bolk$ 6g t.go && 6l t.6 && ./6.out
panic: runtime error: index out of range

runtime.panic+0xac /Users/bolk/go/src/pkg/runtime/proc.c:1083
    runtime.panic(0x11ea8, 0xf8400011f0)
runtime.panicstring+0xa3 /Users/bolk/go/src/pkg/runtime/runtime.c:116
    runtime.panicstring(0x295e6, 0xce63)
runtime.panicindex+0x25 /Users/bolk/go/src/pkg/runtime/runtime.c:73
    runtime.panicindex()
main.main+0x5e /Users/bolk/Проекты/Sample/t.go:6
    main.main()
runtime.mainstart+0xf /Users/bolk/go/src/pkg/runtime/amd64/asm.s:77
    runtime.mainstart()
runtime.goexit /Users/bolk/go/src/pkg/runtime/proc.c:150
    runtime.goexit()
----- goroutine created by -----
_rt0_amd64+0x8e /Users/bolk/go/src/pkg/runtime/amd64/asm.s:64

То есть возникает ошибка, которая аварийно завершает выполнение программы. Самый простой способ этого добиться — вызов «panic», более реальный — обратиться за границу массива:

package main

func main() {
    arr := []int{1,2,3}

    print(arr[1000])
}

«Паника», конечно, ситуация исключительная, программист должен постараться не допускать ситуации, в которых она возникает, но всё же, программа «паникует», а нам нужно продолжить её выполнение, например, чтобы произвести какие-то завершающие действия. Что делать?

Я спросил пару человек, незнакомых с «Go», что, по их мнению, язык должен бы предоставить программисту для решения этой проблемы, оба человека назвали обработку исключений. К сожалению, тут исключений нет. Подход к решению этой проблемы у «Go» чем-то напоминает Visual Basic, а именно конструкцию On Error GoTo line.

В «Go» есть поистине замечательная штука — defer, с её помощью можно вызвать функцию когда управление покидает определённую зону видимости. Для языка без деструкторов (а в «Go» их нет) это очень удобно — можно закрывать файлы, сокеты и прочее в таком полуавтоматическом режиме — важно не забыть поставить нужный вызов только один раз.

Функции с defer будут вызваны когда управление покидает выбранную нами зону по любой причине, в том числе, когда произошла «паника». В функции можно проверить, была ли она вызвана нормальным ходом событий или что-то пошло не так, вызов этой проверочной инструкции (он называется «recover») «успокаивает панику».

Выглядит это вот так:

package main

func main() {
    defer func() {
        if r := recover(); r != nil {
            print("Recovered")
        }
    }()

    arr := []int{1,2,3}

    print(arr[1000])
}

Происходит тут следующее: в зоне вызова функции «main» (тут, как в Си, при запуске вызывается «main») определена анонимная функция, которая тут же вызвана как defer, при возникновении ошибки (обращение за границу массива в предпоследней строке), управление покидает зону видимости функции «main» из-за чего вызывается наша анонимная функция, которая проверяет из-за чего она была вызвана (присваивает значение, полученное из recover, переменной „r“ и проверяет его на nil), чем «успокаивает панику», если она была, и выводит сообщение «Recovered» в этом случае.

6 комментариев

Язык программирования «Go»

В этом году я решил выучить язык программирования «Go». Язык разработан в стенах «Гугла» и это один из проектов, который у «Гугла», что называется, «не взлетел». В компании не очень-то занимаются его продвижением и поддержкой, если ничего не изменится, то язык, по всей видимости, будет обречён на забвение.

Самое интересное в языке — это то, как он запросто справляется с параллелизацией задач, но я пока не успел это попробовать, видел лишь на примерах. Выглядит очень круто. Интересен он ещё и тем, что компилируется (архитектуры AMD64, i386 и ARM), но при этом сам умеет управлять памятью. Ошибочно считается, что в языке нет «традиционных» указателей (с арифметикой), это не так, там они есть, просто спрятаны в модуль.

Компиляция, как утверждается, даёт хороший прирост скорости (всего лишь в 5-10 раз медленее Си и в сотни раз быстрее PHP). При этом моя небольшая программа (см. ниже) скомпилировалась в файл размером 5,8 мегабайт.

Язык сильно меняется со временем, можно заметить, что примеры кода, которым только год, уже не компилируются. Документация по некоторым модулям не просто скудная, её нет, взгляните, к примеру, на функцию Accept модуля syscall.

Вообще, в сообществе «Go» такое уныние и запустение, что мне приходится перебарывать себя, я уже думаю, что зря выбрал этот язык для изучения.

Чтобы было проще учить, я написал программу, которая представляет собой небольшой веб-сервер, дающий доступ на чтение через браузер к папке, в которой программа была запущена. Эту задачу мне давно хотелось решить, мне это нужно время от времени.

Оказалось, что на «Go» программа решается буквально в несколько строк, поэтому это было неинтересно. Я усложнил задачу — после запуска программа должна была сказать по какому IP-адресу можно будет зайти браузером.

Читать дальше.

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

Я использовал модуль syscall и вызвал системный вызов ioctl с параметром SIOCGIFCONF. Из-за того, что язык самовольно выравнивает структуру (struct) по границе 8 байт (надо бы проверить будет ли тот же эффект на архитектуре с 32 битами), я очень долго не мог понять почему мой вызов даёт ошибку.

Дальше всё просто — я получаю все адреса IPv4 (можно и IPv6, но мне пока не хочется возиться), если их несколько, то убираю из них те, что относятся к Private IP (10/8, 172.16/12, 192.168/16) и возвращаю один из тех, что остались. В противном случае, возвращаю тот единственный, что у меня в наличии.

Сама программа ниже:

// httphere 0.1. Написана Евгений Степанищевым, май 2011. //bolknote.ru
package main

import (
    "http"
    "flag"
    "strconv"
    "os"
    "syscall"
    "unsafe"
    "strings"
    "path"
    "path/filepath"
)

const IF_BUFFER int = 2048

type ifconf struct {
    _   int32
    len int32              "Размер буфера"
    ifr unsafe.Pointer  "Указатель на буфер"
}

func exit(message string, code int) {
    os.Stderr.WriteString("Error: " + message + "\n")
    os.Exit(code)
}

/* Проверка входного параметра (порт) на допустимые значения */
func checkargs(port int) {
    if port > 65535 || port < 1 {
        exit("invalid port range", 1)
    }

    if syscall.Geteuid() > 0 && port < 1024 {
        exit("if you want to use ports lower than 1024, you must run httphere as the root user", 3)
    }
}

/* преобразование IP-адреса в строку */
func joinip4(ip []byte) string {
    var ipstr []string

    for _, s := range ip {
        ipstr = append(ipstr, strconv.Itoa(int(s)))
    }

    return strings.Join(ipstr, ".")
}

/* получаем список IPv4-адресов на внешних интерфейсах */
func getallip4() (iplist []([]byte)) {
    fd, errno := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
    if err := os.NewSyscallError("socket", int(errno)); err == nil {
        defer syscall.Close(fd)

        ifr := new([IF_BUFFER]byte)
        ifc := ifconf{len: int32(unsafe.Sizeof(*ifr)), ifr: unsafe.Pointer(ifr)}

        ifcwopadding := uintptr(unsafe.Pointer(uintptr(unsafe.Pointer(&ifc)) + 4))
        _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.SIOCGIFCONF), ifcwopadding)

        if err := os.NewSyscallError("ioctl", int(errno)); err != nil {
            return
        }

        for i := int32(0); i < ifc.len; {
            i += 16 /* skip interface name */
            length, family := int32(ifr[i]), ifr[i+1]

            sockaddr := ifr[i+2:i+length]
            i += length

            if family == syscall.AF_INET {
                iplist = append(iplist, sockaddr[2:6]) 
            }
        }
    }

    return
}

/* на вход подаётся список IPv4-адресов, если их несколько, то функция выбрасывает локальные.
   на выходе — один IPv4 
*/
func filterip4(iplist []([]byte)) []byte {
    switch len(iplist) {
        case 0:
            return []byte{127, 0, 0, 1}

        case 1:
            return iplist[0]
    }

    var private []byte = nil

    for _, ip := range iplist {
        if ip[0] == 192 && ip[1] == 168 || ip[0] == 10 || ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31 {
            private = ip
        } else if ip[0] != 127 {
            return ip
        }
    }

    if private != nil {
        return private
    }

    return iplist[0]
}

func main() {
    port := flag.Int("port", 8080, "http port address (1-65535)")
    flag.Parse()
    checkargs(*port)

    portstr := strconv.Itoa(*port)
    ip := joinip4(filterip4(getallip4()))

    os.Stdout.WriteString("Listen on " + portstr + ". Go to http://" + ip + ":" + portstr + "/\n")

    http.Handle("/", http.HandlerFunc(handler))
    if err := http.ListenAndServe(":" + portstr, nil); err != nil {
        exit(err.String(), 2)
    }
}

func handler(w http.ResponseWriter, r *http.Request) {
    cwd, _ := os.Getwd()
    path := path.Join(cwd, filepath.Clean(r.URL.Path))

    http.ServeFile(w, r, path)
}

У программы есть единственный параметр (он выводится, если запустить программу с ключом „—help“) — можно указать порт, который программа будет слушать (по умолчанию использутеся 8080).

Программа прерывается по Ctrl+C.

17 комментариев

CSSO

Ребята из симферопольского «Яндекса» сказали новое слово в оптимизации CSS — CSSO. Оптимизатор интересен тем, что умеет минимизировать с изменением структуры — то есть удалять перекрываемые свойства, производить слияние блоков с одинаковыми селекторами и тому подобное.

Сергей Крыжановский, который занимается программированием CSSO, в своём блоге хорошо описал отличия CSSO от YUI Compressor, CSSTidy и им подобных.

Например, CSSO умеет вот такое:

/* до CSSO */
.a {
    color: red;
    border: 0;
}

.b {
    color: red;
}

/* после CSSO */
.a{border:0}.a,.b{color:red}

Меня несказанно печалит, что оптимизатор написан на JavaScript, потому что хостинг с серверным JS найти крайне проблематично, но зато оптимизацию в действии можно попробовать прямо в браузере.

42 комментария

CSS и JS в одном файле

Раз уж я затронул тему снижения числа коннектов к серверу при загрузке сайта, то было бы уместо вспомнить незаслуженно забытую технику — объединение JS и CSS в одном файле.

Насколько я знаю, первым эту технику исследовал некий ShivaP в статье «Combine CSS with JS and make it into a single download!» ещё четыре года назад.

Идея такова: в файле специальными ухищрениями помещается и код JavaScript, и код CSS, файл подключается два раза — один раз через тег SCRIPT, второй раз через тег LINK (как CSS), браузер грузит файл один раз, а второй раз берёт его уже из кеша.

Автор проверил свою идею на двух браузерах — IE и FireFox, на том и остановился.

Я проверил эту идею на гораздо бо́льшем числе браузеров (обнаружил проблемы на некоторых «Хромах» и исправил), а так же немного её видоизменил, разрешив использовать многострочный CSS без особых неудобств. Последнее нужно персонально мне, так как у меня нет возможности прогонять свой CSS через оптимизаторы и вытягивать таким образом код CSS в одну строку, а я хочу использовать эту технику у себя на сайте.

У техники есть ограничение, которое не портит много крови, но о нём необходимо знать — в тексте CSS и JS не должна встречаться конструкция «*/». Если в CSS достаточно просто выкусить все комментарии («*/» там, можно считать, нигде больше встретиться не может), то в JS за этим придётся следить, так как такая комбинация вполне может встретиться, в частности, в регулярных выражениях. Например: var regexp = /smth*/g;

В таких случаях звёздочку необходимо прятать (например, заменять её в регулярных выражениях на \x2A или разбивать строку: «smth*» + «/»).

Как ShivaP, первый исследователь этой техники, решил проблему размещения JS и CSS в одном файле можно почитать в оринальной статье, я же поступил несколько иначе, но сходным образом. Конструкции размещаются так, чтобы JS оказывался в комментариях CSS и наоборот. Я похожими вещами уже занимался неоднократно.

Мой код выглядит следующим образом (я поставил рядом две картинки, чтобы можно было сравнить как один и тот же код воспринимается интерпретатором JavaScript и парсером CSS):

JS и CSS (15.58КиБ)

Как видите, принцип довольно несложен.

Теперь несколько слов о том как это всё подключается:

<link type="text/css" rel="stylesheet" href="core.jscss" />

<script type="text/javascript" src="core.jscss#2"></script>

Обратите внимание на «#2» после core.jscss, этот хак позволяет обойти странное поведение некоторых версий «Хрома» — без него браузер не делал попыток подгрузить файл ещё раз и JavaScript не выполнялся.

Немаловажно так же, что заголовок «content-type» должен быть выставлен в «*/*», чтобы избежать проблем с FireFox 2.

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

Читать дальше

<?
ob_start();
$dir     = 'media/';
$expires = 10 * 365 * 24 * 60 * 60; // 10 years

$files = explode(',', str_replace('..', '', $_SERVER['QUERY_STRING']));
$css = $js = array();

foreach ($files as $file) {
   switch (pathinfo($file,  PATHINFO_EXTENSION)) {
       case 'css':
          $css[] = $dir . $file;
          break;

       case 'js':
          $js[] = $dir . $file;
          break;
    }
}

$css = @array_map('file_get_contents', array_unique($css));
$js  = @array_map('file_get_contents', array_unique($js));

$css = implode($css);
$js  = implode($js, ';');

// removes /* comments */ from css
$css = preg_replace('!/\*.*?\*/!s', '', $css);

// removes only first /* comment */ from js
$js  = preg_replace('!^/\*.*?\*/!s', '', $js);

echo <<<JSCSS
// /*
$js
"*/{"/*"}
$css
*/
JSCSS;

$len  = ob_get_length();
$txt  = ob_get_clean();
$etag = '"' . sha1($etag) . '"';

header("Content-type: */*; charset=utf-8");
header("Expires: " . gmdate('D, d M Y H:i:s \G\M\T', $_SERVER['REQUEST_TIME'] + $expires));
header("Cache-control: max-age=$expires, public");
header("Content-length: " . $len);
header('ETag: ' . $etag);

if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
    list ($hash) = explode(';', $_SERVER['HTTP_IF_NONE_MATCH']);

    if ($hash == $etag) {
        header('HTTP/1.1 304 Not modified, thanx for yr question');
        exit;
    }
}

echo $txt;

Формат вызова простой: скрипту через запятую перечисляются файлы, которые нужно объединить и подключить (файлы берутся из папки «media», см. переменную «$dir»):

/util/js+css.php?core.js,core.css,another.css,another.js

35 комментариев

Увеличение числа параллельных соединений браузера к одному домену из HTML

Большинство разработчиков сайтов уже знают, что число соединений к серверу у браузеров лимитированно. Старые версии браузеров используют только два соединения на сервер, более свежие побольше (например, «Опера» 11.10 — аж 16).

В общем случае алгоритм использования этих соединений достаточно сложен и отличается от браузера к браузеру (имеется ввиду будут ли параллельно загружаются стилевые таблицы, скрипты и прочее), но общее правило неизменно — больше своего лимита браузер соединений к серверу не плодит.

Между тем, это не всегда хорошо, ведь если все соединения заняты, то браузер ждёт данных по ним, не инициируя новые, даже если канал до сервера позволяет загрузить что-то ещё.

Если сайт и канал вполне выдержат и большее количество соединений от одного пользователя (с учётом посещаемости), то есть хитрость, которую часто используют — загружать статику (картинки, файлы стилей и прочее) с других доменов. Их не обязательно располагать на других серверах, достаточно дать одному серверу несколько имён и использовать их для загрузки.

Выглядеть это может так: static1.example.org, static2.example.org и так далее. «Яндекс», к примеру, грузит свои данные с домена yandex.st. Как видите, подходит другой домен любого уровня.

Хорошо, когда есть возможность завести любое количество доменов какого-нибудь уровня на своём хостинге. Далеко не все хостеры предоставят такую возможность бесплатно, а простому блогеру платить за ещё одно имя только ради ускорения загрузки вряд ли захочется.

Ничего страшного, чаще всего у пользователя такого хостинга аж четыре домена, просто мало кто об этом подозревает. Первые два достаточно очевидны — хостер часто, кроме какого-нибудь example.org, заводит ещё и www.example.org. Но ведь это два разных домена!

Ещё два не так очевидны— это домены «example.org.» и «www.example.org.». Обратите внимание на точку в конце домена.

Мои эксперименты (проверял на сервисе BrowserShots, операционные системы Linux, Windows XP, Mac OS X) говорят о том, что браузеры умеют с ними работать, то есть картинки с URL, где имя домена кончалось на точку, грузились нормально.

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

В эксперименте я выставил максимальное количество соединений к серверу в единицу («Опера» и «FireFox» позволяют это сделать, где это делается в браузерах на WebKit я не знаю, подскажите, если знаете) и создал две HTML-страницы: одну, где две картинки из моего скрипта грузятся с одного домена, вторую, где одна из них грузится с домена с точкой.

Варианты загрузки (49.40КиБ)

На скриншоте хорошо заметен результат и видно где какой эксперимент (по колонке «Домен»). По скриншоту видно, что я использовал хорошо всем знакомый FireBug, но на Dragonfly из «Оперы» картина такая же.

26 комментариев

«Мак» для программиста

В комментариях кто-то мне писал, что «Мак» не очень-то подходит для программиста. Кому как, а под мои скромные нужды всё есть из коробки:

bolk-osx:~ bolk$ java -version 2>&1 | head -1
java version "1.6.0_24"

bolk-osx:~ bolk$ httpd -v | head -1
Server version: Apache/2.2.17 (Unix)

bolk-osx:~ bolk$ sqlite3 -version
3.6.12

bolk-osx:~ bolk$ ruby -v
ruby 1.8.7 (2009-06-12 patchlevel 174) [universal-darwin10.0]

bolk-osx:~ bolk$ python -V
Python 2.6.1

bolk-osx:~ bolk$ perl -v | head -2 | tail -1
This is perl, v5.10.0 built for darwin-thread-multi-2level

bolk-osx:~ bolk$ bash --version | head -1
GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin10.0)

bolk-osx:~ bolk$ php -v | head -1
PHP 5.3.4 (cli) (built: Dec 15 2010 12:15:07)

Ну и так далее.

19 комментариев

← 1 40 41 42 43 44 67 →