googlego
Google Go vs. Си
Ребята на работе
Я подключился и написал пример на Google Go:
package main
import "fmt"
func main() {
x := []int{2}
for i := 3; i < 200000; i += 2 {
simple := true
for _, j := range x {
if i % j == 0 {
simple = false
break
}
}
if simple {
x = append(x, i)
}
}
fmt.Println(x)
}Запускалось всё
Python — 25,95 секунды Perl — 22,96 секунды PHP — 21 секунда Objective C — 9,40 секунды JavaScript (V8) — 4,73 секунды Java — 1,94 секунды Си — 0.95 секунды Google Go — 0,71 секунды.
Особенно меня поразили «Гоу» и JavaScript.
«Go»: недостатки
Я уже успел написать на «Go» пару тысяч строк (не считая удалённого кода) и должен сказать, что этот язык мне нравится. Этакий «Си» с человеческим лицом. Правда обработка ошибок через panic/defer мне до сих пор кажется сомнительным удовольствем.
Почему же этот язык столько непопулярен?
Самая главная — язык до сих пор не стабилизировался. К пример, пятого мая этого года вышел релиз №57), где кардинально изменился способ которым можно проверить закрылся ли канал. Вместо функции closed канал просто возвращает два значения. Из других примеров могу вспомнить как после очередного обновления компилятора мне пришлось везде в коде заменить вызов Open на OpenFile.
Вторая сильная причина — мало документации. По языку есть только одна книга, о которой мало кто знает (кстати, рекламирую: «Learning Go» Miek Gieben, книга на английском), в официальной документации по модулям чаще всего перечислены только методы,
Третья причина менее очевидна, чем предыдущая. Я называю «Go» языком «Си» с человеческим лицом, при это «Go» отстаёт от «Си» отсутствием предпроцессора. Когда я делал MakeCorner мне хотелось сделать кроссплатформенное определение количества процессоров (сейчас этот код удалён), для этого пришлось использовать вызовы, которые есть на одно системе, но отстутствуют на другой.
В «Go» это означает ошибку при компиляции. Нет никакого внутреннего средства для того, чтобы сообщить компилятору «не смотри сюда, если это freeBSD». Эту ситуацию мне пришлось разрешать при помощи файла make.
Нет худа без добра, вчера ночью я
Но. Это очень мешает и раздражает, особенно когда переходишь с такого динамического языка как «Пайтон». В больших проектах это должно вносить ужасную путаницу, так как придётся готовить несколько файлов для разных платформ (я так и сделал в своём проекте) и выделять куски кода в эти файлы
У меня уже есть задумка что писать на «Go» дальше. Правда я в сомнениях. «Go» настолько непопулярен, что выгоднее делать следующий проект
MakeCorner: теперь уголки мне нравятся
Программа, кстати, автоматически должна стать мультплатформенной. Я не пробовал, но компилятор Go есть для MacOS, FreeBSD, Linux и Windows (и трёх типов архитектур — ARM, i386
Правда, для работы требуется библиотека GD, а она вряд ли скомпилируется на ARM.
«Go»: два новых метода в gd-go: SmoothArc и SmoothEllipse
Добавил в свой проект
MakeCorner на «Go»
У меня есть такая программа в проектах «MakeCorner», я ей фотографии готовлю перед публикацией, она скругляет уголки и оптимизирует изображения.
Когда я начал только её писать (PHP, перегнанный в exe), меня в комментах спросили будет ли версия для других ОС. Я ответил, что не будет, так как не знаю способа перегнать PHP в ELF или, скажем,
Некоторое время назад я начал писать MakeCorner на Go, а недавно выкатил весь проект на ГитХаб (до того времени он у меня жил в локальном репозитории).
Там есть почти всё, что в версии на PHP, кроме (пока) сохранения EXIF. Ключи совместимы (есть одно отличие: для ключа width нужно задавать ноль, чтобы ширина автоматически сохранялась). Кроме того, скругление уголков я переписал, теперь это делается качественнее.
Небольшая инструкция по сборке.
Если, вдруг, будете собирать под своей ОС, там есть Makefile, в нём возможно нужно будет исправить три переменные в начале. В системе должна присутствовать установленная библиотека GD.
Файл jpegtran.bz2 надо будет заменить, там должен лежать скопилированный и сжатый bzip2 файл jpegtran для вашей платформы (для Windows можно найти уже скомпилированный).
Вообще, я думаю над тем, чтобы не класть запускаемый файл внутрь моего кода и потом его оттуда доставать, а подключить к проекту libjpeg, которой пользуется jpegtran, но ещё не решил стоит ли геморрой свеч.
StackBlur в go-gd
Портировал алгоритм Stack Blur с языка Processing на Google Go, теперь в моей библиотеке
Методы Smooth и GaussianBlur, которые я портировал из форка
Вот небольшое описание этого алгоритма от автора, Марио Клингемана:
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»: количество го-программ
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).
В документации есть множество мест, где говорится, что сейчас планировщик
Возможно я ошибаюсь, но мне кажется очевидным, что значение этой директивы должно быть уж никак не меньше числа процессоров. Чтобы автоматически определять количество процессоров и указывать верное, по моему мнению, значение, я написал следующий код. Возможно ещё
package main
import ("syscall"; "runtime")
func main() {
if n, _ := syscall.SysctlUint32("hw.ncpu"); n > 0 {
runtime.GOMAXPROCS(int(n))
}
}Развитие биндинга графической библиотеки «GD» для «Go»
Несколько лет назад авторы языка PHP форкнули библиотеку GD, биндинг к которой для языка Go я вчера написал, и стали развивать собственную версию. В язык PHP входит именно форк.
Исходная библиотека беднее, например, там нет вызова ImageConvolution и всего того, что с ним можно делать (размытие, выделение краёв и так далее), а мне хочется, чтобы в моём биндинге они были. Можно, конечно, выдрать GD из РHP, но я хочу написать
Почесав репу, я потратил пару часов, переписал часть
Биндинг графической библиотеки «GD» для «Go»
Написал за часть ночи и часть утра биндинг к графической библиотеке «GD»:
Дело в том, что несмотря на существование модуля «image», в «Go» нет почти ничего для работы с графикой, всё что умеет язык — читать и писать JPEG и PNG (скоро появится поддержка GIF и TIFF), копировать области, получать значение цвета в заданных координатах, рисовать прямоугольники и точки. Это всё.
А мне, по условиям программисткой игры, в которую я ввязался, нужно уметь работать с графикой куда более разнообразно, например, изменять размеры изображения (resize image). Единственная сторонняя библиотека для этого, которая существует — «scale», сильно портит картинку.
Биндинги писать на «Гоу» чрезвычайно просто, единственное — язык запрещает читать переменную сишную errno и я не нашёл никакого способа получить её значение.
Покрытие «GD» у меня не 100%, есть функции, которые я просто поленился реализовывать. Руководства к пользованию нет, есть небольшой пример использования, makefile для его сборки и к модулю «GD» для PHP, там почти всё совпадает (только у меня функций намного меньше, GD в PHP не тот, что использую я, а форк).
Сколько занимает программа на «Go»
Хочу немного развеять тот миф, который я невольно создал — что скомпилированные программы, написанные на языке «Go» занимают очень много. Мой скомпилированный пример
На самом деле, всё не так просто. Простенькая программа на «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При увеличении исходного кода размер вырастает незначительно, в моей шестимегабайтной программе большую часть занимает
Сейчас я пишу программу где уже 414 строк (в httphere — 132) и подключено 11 стандартных модулей, включая модуль для работы с изображениями («image»), скомпилированная версия занимает 1,5 мегабайта. Хотел бы попробовать сжать файл архиватором UPX, но, к сожалению, он не понимает тип запускаемых файлов моей системы
«Go»: чтение ini-файлов
Мне тут для программы на языке «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), ключами верхнего уровня является имя секции (если отсутствует, то имя пустое), ключами второго уровня — ключи
«Go»: обработка ошибок
После первого прочтения документации по языку «Гоу» у меня сложилось неправильное впечатление о конструкции «panic», отчасти
Моё небольшое исследование показало, что это не так, «panic» — это стандартное средство генерации ошибки (аналог исключений), тогда как «defer» плюс «recover» — стандартный способ сгенерированную ошибку отловить и обработать.
Напомню как это делается. Генерация ошибки осуществляется вызовом «panic», перехват —
Например:
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“))», тип ошибки можно узнать, так же как и её содержимое. Это уже больше похоже на исключения.
Если внимательно посмотреть на мой пример выше, то не совсем ясно следующее: «паника» привёла к выполнению анонимной
Способ есть, давайте разберём его в другом примере. В нём же я покажу как именно определить тип «паники».
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». Если мы
Что происодит дальше. Вызов «panic» принимает на вход «interface{}», то есть, фактически, параметр любого типа. Чтобы определить тип этого параметра, используется специальнуя конструкция «переменная.(type)» (которая, к слову, может использоваться только в операторе «switch»).
Упомянутая конструкция может определить какой тип ошибки нам вернулся. Похожая конструкция
Полученные значения обрабатываются и сохраняются в переменной «result», которая связана со значением возвращаемым функцией «test», оно и попадает в print функции «main».
Я уже настолько проникся языком, что это решение уже мне не кажется таким странным, как поначалу.
«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 (!), в последнем байте неотображаемый символ
}Послевкусие от «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.
«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»
В «Go» есть поистине замечательная штука — defer, с её помощью можно вызвать функцию когда управление покидает определённую зону видимости. Для языка без деструкторов (а в «Go» их нет) это очень удобно — можно закрывать файлы, сокеты и прочее в таком полуавтоматическом режиме — важно не забыть поставить нужный вызов только один раз.
Функции с defer будут вызваны когда управление покидает выбранную нами зону по любой причине, в том числе, когда произошла «паника». В функции можно проверить, была ли она вызвана нормальным ходом событий
Выглядит это вот так:
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»
Язык программирования «Go»
В этом году я решил выучить язык программирования «Go». Язык разработан в стенах «Гугла» и это один из проектов, который у «Гугла», что называется, «не взлетел». В компании
Самое интересное в языке — это то, как он запросто справляется с параллелизацией задач, но я пока не успел это попробовать, видел лишь на примерах. Выглядит очень круто. Интересен он ещё и тем, что компилируется (архитектуры AMD64, i386 и ARM), но при этом сам умеет управлять памятью. Ошибочно считается, что в языке нет «традиционных» указателей (с арифметикой), это не так, там они есть, просто спрятаны в модуль.
Компиляция, как утверждается, даёт хороший прирост скорости (всего лишь
Язык сильно меняется со временем, можно заметить, что примеры кода, которым только год, уже не компилируются. Документация по некоторым модулям не просто скудная, её нет, взгляните, к примеру, на функцию Accept модуля syscall.
Вообще, в сообществе «Go» такое уныние и запустение, что мне приходится перебарывать себя, я уже думаю, что зря выбрал этот язык для изучения.
Чтобы было проще учить, я написал программу, которая представляет собой небольшой
Оказалось, что на «Go» программа решается буквально в несколько строк, поэтому это было неинтересно. Я усложнил задачу — после запуска программа должна была сказать по какому Никаких модулей в языке для простого решения этой задачи нет, более того, на решение этой небольшой задачи у меня ушло около пяти часов. Вчера я не мог уснуть, Я использовал модуль syscall и вызвал системный вызов ioctl с параметром SIOCGIFCONF. Дальше всё просто — я получаю все адреса IPv4 (можно и IPv6, но мне пока не хочется возиться), если их несколько, то убираю из них те, что относятся к Private IP (10/8, 172.16/12, 192.168/16) и возвращаю один из тех, что остались. В противном случае, возвращаю тот единственный, что у меня в наличии. Сама программа ниже: У программы есть единственный параметр (он выводится, если запустить программу с ключом „—help“) — можно указать порт, который программа будет слушать (по умолчанию использутеся 8080). Программа прерывается по Ctrl+C.Читать дальше.
// 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)
}