«Не звони и не пиши мне больше!!!!» — пришла смс-ка от моей девушки Кати. Через пару часов я осознал, что теперь у меня появилась куча свободного времени и я решил переписать Dcoin на Go.

Введение


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

Для тех, кому интересно, что там с Катей, я сделал спойлеры, ну а кому не интересно, просто не обращайте внимание на Катю.

Итог 8 месяцев: приложение работает на Win (64/32), OSX(64/32), Linux(64/32), FreeBSD(64/32), Android, IOS (будет круто, если кто-то закинет в App Store).
Общего кода ~73к строк, кода под разные ОС где-то несколько сотен строчек.
40к — обработка/генерация блоков/тр-ий, 17.5к — контроллеры для интерфейса, 15.5к — шаблоны
Поддерживаются PostgreSQL, SQLite, MySQL.

Тех, кто будет тестировать мое творение, предупреждаю — могут быть баги, и если у Вас есть время, чиркните о них, пожалуйста, на darwin@dcoin.club или в личку на хабре. Пожелания и советы тоже приветствуются.

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

Старт


Прошло несколько часов после смс-ки на Кати. Решение переписать Dcoin уже было принято. Нужно было с чего-то начать. Начал гуглить, как изучить Go с нуля. Где-то 2-3 дня штудировал http://golang-book.ru/ и https://tour.golang.org/, затем скачал вот эту книгу, на неё ушло где-то 2 недели, выполнял только примеры, самостоятельную работу пропускал. Было сложно, но интересно. Хотелось уже как можно скорее приступить к переписанию моих PHP-исходников.

Ничего нового я уже давно не изучал, мозг не привык к такому режиму. Купил в аптеке Омегу-3 и Ноотропил, у меня под ними лучше инфа запоминается, хотя может это и самовнушение.

Дочитал последнюю главу. Наконец можно было приступить к самому интересному.
Задача вполне понятная: переписать несколько десятков тысяч строк PHP-кода. Но с чего начать, совершенно не понятно. Решил начать с самого простого — с веб-сервера.

Про Катю
Расскажу сперва как всё началось. Познакомился я с ней в интернете на сайте знакомств в начале 2015-го. Влюбился почти сразу. Уж больно внешне она была в моем вкусе. Гуляли по парку, шутили, смеялись, прикалывались, стало холодно, расставаться не хотелось. Позвал к себе мультики смотреть. Сели в такси, приехали. Включил «Три богатыря: Ход конем», очень смешной мультик, лежали на кровати, смеялись до слез.


Revel


По описанию не очень понял, для чего он. Поставил, поэкспериментировал, стало ясно — точно не подходит. Revel для сайтов.

Beego


Работает на модулях. Есть неплохие доки на русском. Я взял 2 модуля config и session.

Gorilla


mux — хорошая штука, но применения для себя найти не смог.

В итоге остановился на чистом net/http + html/template

Веб-сервер


Свой веб-сервера в Go поднять очень просто. Вот пример файлового сервера:

package main
import "net/http"
func main() {
	changeHeaderThenServe := func(h http.Handler) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			h.ServeHTTP(w, r)
		}
	}
	http.Handle("/", changeHeaderThenServe(http.FileServer(http.Dir("."))))
	http.ListenAndServe(":8000", nil)
}
  

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

Вызов контроллера по переданному имени


У меня получилось 170 контроллеров. Каждый контроллер вызывается через один из 6-и HandleFunc, например content.go. Имя контроллера в HandleFunc берется из POST или GET запроса вот так tplName := r.FormValue(«tpl_name»). Затем вызывается CallController(c, tplName), который вызывает один из 170 контроллеров, который в свою очередь выдает готовый html-код, а content.go получив HTML записывает его через w.Write([]byte(html)) и выдает браузеру. Ну как-то так.

Про Катю
Поздний вечер, я уверен, что Катя останется на ночь. Но она почему-то стала твердить, что ей надо домой. Я сказал, что хочу накормить её завтраком в постели. Договорись, что завтра утром она приедет завтракать. Вызвал такси, она уехала. Уснул счастливым, т.к. нашел себе красивую девушку, с которой весело и интересно. На следующий день от неё пришла смс-ка «Привет. Чем занимаешься?». Помню свои ощущения, ведь мне написала девушка, которая мне очень нравится, это было кайфово. И скоро мы с ней должны были снова встретиться…


Сессии


Благодаря модулю session от beego всё очень просто

// Сессии у меня хранятся в памяти, но можно хранить в файлах или БД
globalSessions, _ = session.NewManager("memory", `{"cookieName":"gosessionid","gclifetime":86400}`)
go globalSessions.GC()
// Пишем в сессию  
sess.Set("username", 1000)
sess, _ := globalSessions.SessionStart(w, r)
defer sess.SessionRelease(w)
// Читаем из сессии
username := sess.Get("username")


Обработка файлов


В контроллере upload_video.go мне понадобилось принять файл.

// Выделяем память под файл. 32Mb
r.ParseMultipartForm(32 << 20)
// Получаем файл в multipart.File
file, _, _ := r.FormFile("file")
buffer := new(bytes.Buffer)
// Копируем в буфер
_, err = io.Copy(buffer, file)
defer file.Close()
// Сам файл
binaryFile = buffer.Bytes()
// Название файла
fileName := r.MultipartForm.File["file"][0].Filename
// Content-Type
contentType := r.MultipartForm.File["file"][0].Header.Get("Content-Type")  


Код сервера, который получает от клиента 3gp и возвращает mp4 (вдруг кому-то пригодится)
package main

import (
	"io"
	"io/ioutil"
	"fmt"
	"net"
	"github.com/c-darwin/dcoin-go/packages/utils"
        "os"
	"os/exec"
)

func handleRequest(conn net.Conn) {
	// размер данных
	buf := make([]byte, 4)
	n, err := conn.Read(buf)
	if err != nil {
		fmt.Printf("%v", utils.ErrInfo(err))
	}
	size := utils.BinToDec(buf)
	fmt.Printf("get data size: %v / n: %v\n", size, n)
	if size < 10485760 {
		// сами данные
		binaryData := make([]byte, size)
		n, err = io.ReadFull(conn, binaryData)
		fmt.Printf("n: %v\n", n)
		if err != nil {
			fmt.Printf("%v", utils.ErrInfo(err))
		}

		gp3, err := ioutil.TempFile(os.TempDir(), "temp")
		if err != nil {
			fmt.Printf("%v", utils.ErrInfo(err))
		}
		mp4, err := ioutil.TempFile(os.TempDir(), "temp")
		if err != nil {
			fmt.Printf("%v", utils.ErrInfo(err))
		}
		err = ioutil.WriteFile(gp3.Name()+".3gp", binaryData, 0644)
		if err != nil {
			fmt.Printf("%v", utils.ErrInfo(err))
		}
		out, err := exec.Command("/usr/bin/ffmpeg", "-i", gp3.Name()+".3gp", mp4.Name()+".mp4").Output()
		if err != nil {
			fmt.Println("/usr/bin/ffmpeg", "-i", gp3.Name()+".3gp", mp4.Name()+".mp4")
			fmt.Printf("%v\n", utils.ErrInfo(err))
		}
		fmt.Printf("out: %v\n", out)
		
		data, err := ioutil.ReadFile(mp4.Name()+".mp4")
		if err != nil {
			fmt.Println(err)
		}
		// в 4-х байтах пишем размер данных, которые пошлем далее
		size := utils.DecToBin(len(data), 4)
		n, err = conn.Write(size)
		if err != nil {
			fmt.Println(err)
		}
	  	fmt.Printf("n: %v\n", n)
		
		// далее шлем сами данные
		n, err = conn.Write(data)
		if err != nil {
			fmt.Println(err)
		}
	  	fmt.Printf("n: %v\n", n)

	}
}

func main() {
    // включаем листинг TCP-сервером и обработку входящих запросов
    l, err := net.Listen("tcp", ":8099")
    if err != nil {
   	fmt.Printf("Error listening: %v\n", err)
	panic(err)
	os.Exit(1)
    }
    defer l.Close()
    for {
        conn, err := l.Accept()
        if err != nil {
            fmt.Println("Error accepting: ", err.Error())
            os.Exit(1)
        }
        go handleRequest(conn)
    }
}



Преобразование Base64 в картинку


В crop_photo.go мне понадобилось принять картинку в base64 и получить из неё обычный png

b64, _ := base64.StdEncoding.DecodeString(r.FormValue("b64Img"))
img, _, _ := image.Decode(bytes.NewReader(b64))
out, _ := os.Create("img.png")
// в img.png сохраняется наша картинка
png.Encode(out, img)

Про Катю
В тот день мы так и не встретились. И на следующий тоже. Я не знаю, что у неё происходило в голове, но она постоянно переносила свидания. Даже не поздравила с 23 февраля.
Решил её как-то растормошить. Написал Кате смс-ку: «скоро буду, конечно захвачу»
Она ответила: «в смысле?»
Я: «упс, не туда»
Она: «вот значит как»
У неё включилась ревность. Через день предложил встретиться, она согласилась.


Bindata


Когда скомпилил веб-сервер и перенес на одну из нод, то ничего не работало. Выяснилось, что все шаблоны, картинки и пр. сами в бинарник не запаковываются.
Пугуглил, нашел https://github.com/jteeuwen/go-bindata
Тулза очень удобная и простая, она генерит go-файл, где шаблоны, картинки и пр. пишутся в переменные в виде набора байт. В итоге после компиляции получается один бинарник. Если нужно чтобы файлы брались с диска, то надо добавить параметр "-debug=true".
Я использую простенький bash-скрипт, чтобы каждый раз не вводить путь, куда класть go-файл

#!/bin/bash
if [ $# -gt 0 ] && [ $1 = "debug" ] 
then
  DEBUG="-debug=true"
fi
go-bindata -o="packages/static/static.go" -pkg="static" $DEBUG static/...

Затем мне понадобилось, чтобы файлы из директории static можно было получать запросом localhost:8089/static/img.png. Для этого есть go-bindata-assetfs. Вот пример для директории static:

http.Handle("/static/", http.FileServer(&assetfs.AssetFS{Asset: static.Asset, AssetDir: static.AssetDir, Prefix: ""}))

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


Заключение


В следующих статьях я расскажу про html/template, БД, плавное завершение приложения через сигналы, обработку блоков из блокчейна, шифрование в GO и расшифровку в JS, про то, как я, немного изменил gomobile, добавив уведомления и работу в фоне для IOS и Android приложений.

Комментарии (57)


  1. 4mz
    10.12.2015 13:18
    +189

    Я один читал только спойлеры «Про Катю»? )


    1. Yaruson
      10.12.2015 13:26
      +66

      Нет, правда я ещё случайно открыл «Код сервера, который получает от клиента 3gp...»


      1. Igogo2012
        10.12.2015 13:42
        +13

        Аналогично, толкьо про катю и «Код сервера...»


    1. sl4mmer
      10.12.2015 13:49
      +1

      нет, не один


    1. Find_the_truth
      10.12.2015 14:46
      +2

      Ты еще и мой коммент украл -_-


    1. LeXeR3
      10.12.2015 19:30
      +12

      Судя по всему, в следующей статье все ждут, что техническая сторона вопроса будет скрыта спойлерами, а основная история будет посвящена Кате:)


    1. tendium
      11.12.2015 01:03

      Да-да, тема не раскрыта, как это было принято говорить на некоторых почивших ресурсах :)


    1. danfe
      11.12.2015 13:07

      Начинал читать статью, пока сидел в очереди, т.к. с телефона спойлеры почему-то не открывались. С десктопа читал уже только спойлеры, конечно. ;-)


    1. robux
      13.12.2015 19:53

      Будьте мужиками!
      Не ведитесь на женские штучки!
      Программируйте на Ruby и Python, бл!


    1. mmatrosov
      15.12.2015 11:44

      скоро буду, конечно захвачу
      smooth :)


  1. Alexufo
    10.12.2015 13:21
    +37

    Если Катя вернется — пэхапэшникам будет просто больше нечем крыть!


    1. PapaBubaDiop
      10.12.2015 13:46
      +59

      Go, Катя, Go!


  1. LionAlex
    10.12.2015 13:21
    +3

    Статья интересная, ждем продолжения!
    Но судя по коду на гитхабе, Вы забыли прочитать Effective Go


    1. c-darwin
      10.12.2015 13:27
      +3

      Спасибо!
      Да, действительно, не читал. Код у меня ужасный, согласен…


      1. LionAlex
        10.12.2015 15:48

        Не то чтобы ужасный, просто не соответствует каноническому представлению Go-сообщества о хорошем читабельном коде.


  1. Pingvi
    10.12.2015 13:22
    +5

    Ожидал в конце статьи спойлер с фотографией Кати)


  1. dvserg
    10.12.2015 13:37
    +9

    А можно я Вашу историю с Катей на ЯП выложу, только текст про код спрячу под спойлеры?


    1. sl4mmer
      10.12.2015 13:51
      +9

      то есть вы уберете текст статьи в спойлеры а содержимое спойлеров сделаете статьей? Оо


      1. c-darwin
        10.12.2015 13:56
        +3

        Можно :)


  1. schroeder
    10.12.2015 14:15
    +63

    Пожалуй рискну.
    Напишите продолжение про Катю здесь. На продолжение про Go можете забить.


  1. ikirin
    10.12.2015 14:37
    +8

    Окончание Про Катю будет?


    1. c-darwin
      10.12.2015 14:46
      +1

      Всё будет, но не сегодня.


      1. AndrewN
        10.12.2015 16:07
        +12

        Автор перечитал пресловутых правил!


      1. i0ngunn3r
        10.12.2015 18:05

        Ну вот, расходимся!


  1. reaferon
    10.12.2015 14:42
    +8

    Ой, Катя-Катя…
    Как я понимаю, КДПВ уже — вчерашний день?


    1. GamePad64
      11.12.2015 01:15
      +6

      Теперь в технические статьи будут вставлять любовную сюжетную линию. Дожили :)
      Тоже читал только про Катю, да.


  1. harlequin
    10.12.2015 15:15
    +1

    Катя, код сервера и переход на книжку Фэйна… узнал много нового, спасибо :)


  1. Fen1kz
    10.12.2015 15:45
    +4

    Правило #6: Выжди минимум четыре часа прежде чем ответить на первое SMS мужчины, и минимум полчаса, прежде чем отвечать на каждое последующее сообщение

    Я бы обсудил книгу и качество её «правил»


    1. Yaruson
      10.12.2015 16:03
      +5

      То есть, в первую очередь, через пол часа после поступления, следует отвечать на второе из двух отправленных под ряд сообщений, а затем, ещё через три с половиной часа, на первое, правильно? :)


      1. masterL
        10.12.2015 16:59
        +4

        Там только один поток и нет ассинхронности.


        1. Yaruson
          10.12.2015 21:02
          +3

          Хм. Да, легко отделаться после книжки про JavaScript не получилось… :)


        1. Fen1kz
          11.12.2015 13:04

          А мне кажется девушки больше связаны с Promise-based решениями.


        1. bogolt
          11.12.2015 15:07
          +2

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


          1. qw1
            11.12.2015 15:32

            Ждём апдейта от авторов книги, закрывающего эту уязвимость.


    1. bolk
      10.12.2015 21:10
      +1

      Мне кажется это скрытая реклама этой книги.


  1. M0sTH8
    10.12.2015 16:16
    -7

    А есть видео, где Катя программирует?


  1. artem_kovardin
    10.12.2015 17:05

    А зачем для криптовалюты видео загружать?


    1. c-darwin
      10.12.2015 17:30

      Премайна нет. Начальную денежную массу нужно как-то создать. Все видео в паблике. Монеты создаются из ничего у тех, кто выложил видео (доказал, что человек) и пообещал обменять какую-то сумму на Dcoin-ы. В wiki я постарался всё описать и даже нарисовать.


  1. OlegFX
    10.12.2015 18:29
    +19

    Так вот, оказывается, какой он — способ написать свой первый пост про Go, оставшись при этом в плюсе!


  1. webhamster
    11.12.2015 01:02
    +1

    > Dcoin-ы можно обменять на наличные в оффлайне у любого майнера, который находится недалеко от Вас

    И заодно попасть на контрольную закупку, получить админстративку и заплатить штраф в 50 000 руб.

    webhamster.ru/mytetrashare/index/mtb0/1448058944b7i6qbmh7n


    1. c-darwin
      11.12.2015 10:22

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


  1. remirran
    11.12.2015 01:37
    +1

    Ну вот, так бы пролистал и забыл. А теперь жду второй статьи про Катю.


  1. prishelec
    11.12.2015 01:44

    Я не могу читать статью. Спойлер «про Катю» просится его открыть.


  1. DmitryKoterov
    11.12.2015 04:15
    +3

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


    1. vvzvlad
      17.12.2015 17:55

      Недавно? Года три точно есть.


  1. Koshelenok
    11.12.2015 08:22

    Вначале прочитал статью, а затем уже про Катю. Теперь жду продолжения и хэппи енда.


  1. bo883
    11.12.2015 11:27
    +1

    необычная статья, любовь+кодинг, интересный маневр :)


  1. ncix
    11.12.2015 13:14
    +1

    Катю в статью, а код и сопутствующие размышления — в спойлеры.


  1. bogolt
    11.12.2015 15:09

    На рутрекере в теме про ту самую книгу уже три страницы обсуждения Кати.


    1. qw1
      11.12.2015 15:38
      +3

      Оттуда:

      Прочитал книгу пацаны и реально работает.
      Теперь на тикет пишу первый коммент строго спустя 30 минут, а если тикет с высоким приоритетом — то спустя 4 часа. Не поверите — но за всю карьеру руководитель ещё никогда не смотрел на меня так интригующе. А коллеги каким завистливым на меня косятся. Думаю меня ждет пост тим-лида в скорейшем будущем.
      Может кто посоветует ещё что почитать там? Совершенные отношения? Паттерны свиданий? Взаимопонимание — полное руководство?
      (с)quearde


  1. withkittens
    11.12.2015 16:24
    +6

    Автор, надеюсь, вы понимаете, что будет здорово, если Катя окажется настоящей девушкой, а не КДПВ (Катей-для-привлечения-внимания). Хабр вам не простит обмана, заберёт все плюсцы обратно, а также вы запятнаете репутацию всех гоферов как людей, способных на что угодно (даже на Кать!), лишь бы заинтересовать статьёй о Go.


    1. c-darwin
      11.12.2015 16:45
      +2

      Катя настоящая. Всё написанное мною — правда.


      1. 1Tiger1
        11.12.2015 18:36
        +2

        «переписывал свою криптовалюту с PHP на Go» — и это тоже?


        1. c-darwin
          11.12.2015 19:24
          +1

          Ну да. А что Вас смущает?


      1. schroeder
        11.12.2015 22:00
        +1

        Требую доказательств! Я про Катю.


  1. Smile42RU
    18.12.2015 00:34

    Дико радует что зверская работа по переводу доков BeeGo на русский не прошла даром :)
    Приходите к нам, коммитить в BeeGo, там даже Слак для русских завели, с моей подачи, но пока там не очень людно.


    1. c-darwin
      18.12.2015 00:44

      Да, спасибо тем, кто переводил, на русском читать удобней.
      Как закончу баги в Dcoin исправлять, постараюсь заглянуть.