HTTP запросы для Python 2 и Python 3

Для Python существует замечательная библиотека для работы со всеми типами HTTP запросов - Requests, но когда нужно сделать что-то без внешних зависимостей, то встает вопрос велосипедостроения. Проблема еще более усиливается когда необходима одновременная поддержка Python 2 и Python 3.

Стандартная библиотека urllib.urlopen не поддерживает методы для отправки PUT и DELETE запросов, а кроме того в Python 3 перенесли большинство методов из urllib2 в urllib.request, что добавляет некоторые костыли в код, для совместимости с Python 2. Таким образом составил себе список того что мне необходимо:

  • Совместимость Python 2 и 3;
  • Отправка GET, POST, PUT, DELETE запросов;
  • Парсинг JSON ответа.

Посидев пару часов собрал свой велосипед совмещающий в себе все эти требования:

import sys
import json

try:
    # python3
    from urllib.request import build_opener, Request, HTTPHandler
    from urllib.error import HTTPError
    from urllib.parse import urlencode
except ImportError:  # pragma: no cover
    # python2
    from urllib2 import build_opener, Request, HTTPHandler, HTTPError
    from urllib import urlencode


def request(url, method='GET', data=None, headers={}):
    if data is not None:
        data = urlencode(data)
        if method in ['GET', 'DELETE']:
            url = url + '?' + data
            data = None
        else:
            x_www = 'application/x-www-form-urlencoded; charset=utf-8'
            headers.update({'Content-Type': x_www})
            if sys.version_info > (3,):  # python3
                data = data.encode('utf-8')

    try:
        opener = build_opener(HTTPHandler)
        req = Request(url, data=data, headers=headers)
        req.get_method = lambda: method
        response = opener.open(req).read()
        data = json.loads(response.decode('utf-8'))
    except HTTPError as e:
        data = json.loads(e.read().decode('utf-8'))
    except ValueError:
        return False

    return data

Можно легко проверить работу всех этих методов используя сервис httpbin.org

data = {'foo': 'bar'}
headers = {'x-header': 'x-value'}

resp = request('https://httpbin.org/get', data=data, headers=headers)
assert resp['headers']['X-Header'] == 'x-value'
assert resp['url'] == 'https://httpbin.org/get?foo=bar'
assert resp['args']['foo'] == 'bar'

resp = request('https://httpbin.org/post', 'POST', data=data, headers=headers)
assert resp['headers']['X-Header'] == 'x-value'
assert resp['url'] == 'https://httpbin.org/post'
assert resp['form']['foo'] == 'bar'

resp = request('https://httpbin.org/put', 'PUT', data=data, headers=headers)
assert resp['headers']['X-Header'] == 'x-value'
assert resp['url'] == 'https://httpbin.org/put'
assert resp['form']['foo'] == 'bar'

resp = request('https://httpbin.org/delete', 'DELETE', data=data, headers=headers)
assert resp['headers']['X-Header'] == 'x-value'
assert resp['url'] == 'https://httpbin.org/delete?foo=bar'
assert resp['args']['foo'] == 'bar'

Альбом с улиц Los Angeles

Гуляя вечером по улицам Лос-Анджелеса к нам подошел веселый чернокожий парень и стал расспрашивать откуда мы, а потом сам рассказал свою историю, начинающего артиста. Дойдя до этого места он предложил нам поддержать его, купив альбом на простом CD и даже оставил на нем свой автограф в надежде, что он станет знаменитым.

Мы решили поддержать его и купили тот альбом. Теперь же спустя полтора года я так и не смог найти о нем ничего нового кроме аккаунта в Instagram и решил выложить тот альбом на SoundCloud, чтобы послушать мог каждый.

Подпольный рынок кардеров. Перевод книги «Kingpin»

На хабре вышла серия постов с переводом книги Kingpin, читать которую с экрана ноутбука мне совершенно неудобно и единственным логичным решением стал перевод книги в PDF и ePub, для чтения на планшете.

Кевин Поулсен, редактор журнала WIRED, а в детстве blackhat хакер Dark Dante, написал книгу про «одного своего знакомого».

В книге показывается путь от подростка-гика, до матерого киберпахана, а также некоторые методы работы спецслужб по поимке хакеров и кардеров.

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

TL;DR ⭐ kingpin.epubkingpin.pdf

Технические подробности сборки

Задавшись идеей конвертации статей с переводом в PDF я перенес текст каждой главы в отдельные markdown файлы. После этого внес уточнения из комментариев к постам. А завершающим этапом прогнал эти файлы через Яндекс.Спеллер для исправления грубых орфографических ошибок.

После этого имея материал для работы сел и написал скрипт на питоне, собирающий из набора файлов единый PDF.

Для этого все .md файлы конвертируем в соответствующие им html страницы.

CHAPTERS = sorted([os.path.join(APP_PATH, f) for f in glob(CHAPTERS_PATH)])

md = Markdown(output_format="html5")

for chapter in CHAPTERS:
    html = md.reset().convert(open(chapter, 'r').read().decode('utf-8'))
    outhtml = TEMPLATE % html

    filename = os.path.splitext(os.path.basename(chapter))[0] + '.html'
    filepath = os.path.join(HTML_PATH, filename)

    with open(filepath, 'w') as f:
        f.write(outhtml.encode('utf-8'))

На втором этапе установив в систему wkhtmltopdf и библиотеку python-pdfkit собираем единый PDF.

htmlfiles = [os.path.join(HTML_PATH, f) for f in os.listdir(HTML_PATH) if f.endswith('.html')]

options = {
    'page-size': 'B5', # A5
    'margin-top': '0.75in',
    'margin-right': '0.75in',
    'margin-bottom': '0.75in',
    'margin-left': '0.75in',
    'encoding': 'UTF-8',
    'no-outline': None,
    'no-background': None,
}

pdfkit.from_file(htmlfiles, 'Kingpin.pdf', options=options, cover='static/cover.html')

Готовый скрипт для сборки PDF и ePub, инструкции, а также все исходные файлы я выложил на свой Github.

Как я продавал домены через escrow

За последний месяц мне удалось продать два домена воспользовавшись китайским и американским escrow сервисами. Об опыте продажи и отличии сервисов я и хотел бы рассказать.

Сервис escrow выступает доверенной стороной в сделке между продавцом и покупателем, и, посредством залогов, страхует от недобросовестного выполнения условий.

Первый домен я продал очень настойчивому китайцу с фамилией «杨», который просил чтобы сделка была проведена именно через китайский сервис DN.com.

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

  • Для активации аккаунта необходима загрузка двух фотографий: скан ID (паспорт, права и т.д.) и фотография себя держащего этот ID;
  • После внесения оплаты покупателем, сервис помогает в передаче AuthCode домена;
  • Денежные средства можно вывести в $, € или ¥ на PayPal или банковский счет;
  • Комиссию снимают два раза, первый раз с покупателя, за сделку, а второй раз с продавца, за вывод денежных средств;
  • При выводе денег через PayPal, комиссия составила $20, против $25-30 за вывод на банковский счет.

Продавая другой домен я предложил воспользоваться американским сервисом Escrow.com, чтобы иметь представление об отличии от китайского. Процедура затянулась из-за проблем с трансфером домена. Но это не помешало выписать основные различия сервисов.

  • В отличии от DN.com, сервис Escrow.com не требует подтверждение личности, а перед получением денег от покупателя проводится телефонное интервью с продавцом. Основная тема интервью — точное название домена и сумма сделки;
  • После внесения оплаты покупателем сервис никак не помогает в передаче AuthCode домена. А процесс передачи кода должен вестись напрямую с покупателем, по электронной почте;
  • Вывод денежных средств возможен только на банковский счет, комиссия составляет $40, что в два раза превышает китайский сервис.
  • Кроме того, нет возможности держать денежные средства, и сразу по завершению сделки деньги переводятся на банковский счет.

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

Патчим mysqldump в домашних условиях

Бекапы это хорошо, а еще лучше когда они работают так как надо когда они нужны. На одном из проектов понадобилось восстановить дамп из 745 триггеров и накатить их на рабочую MySQL базу. 

MySQL позволяет использовать любые имена триггеров, в том числе с использованием точек (например: analitica.cron.indeg.y.run.a_insert). А mysqldump при создании дампа не учитывает это обстоятельство и добавляет конструкцию для их дропа следующего вида:

/*!50032 DROP TRIGGER IF EXISTS analitica.cron.indeg.y.run.a_insert */;

Подвох ожидает при попытке эти дампы накатить на базу где эти триггеры уже созданы. С точки зрения MySQL этот запрос не дропнет триггер, потому что не найдет триггер с таким именем. Для корректной работы имя триггера должно быть заключено в апострофы.

В попытках найти обходное решение зарепортил соответствующий репорт в баг трекер Перконы, а они уже продублировали аналогичный репорт в официальный трекер MySQL.

Поскольку исправлять этот баг будут долго, а дампы мне нужны прямой сейчас. Решить эту задачу я решил самым прямолинейным способом, а именно самостоятельно пропатчив mysqldump. Для этого склонировал официальный репозиторий перконовского дитрибутива MySQL 5.6 с GitHub’а.

git clone --recursive --depth 1 https://github.com/percona/percona-server/

Открыл файл client/mysqldump.c и добавил апострофы к конструкции DROP TRIGGER IF EXISTS в паре мест. Если посмотреть diff, то получается такой патч:

@@ -3517,7 +3517,7 @@ static void dump_trigger_old(FILE *sql_file, MYSQL_RES *show_triggers_rs,
     fprintf(sql_file, "/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;\n");

   if (opt_drop_trigger)
-    fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS %s */;\n", (*show_trigger_row)[0]);
+    fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS `%s` */;\n", (*show_trigger_row)[0]);

   fprintf(sql_file,
           "DELIMITER ;;\n"
@@ -3604,7 +3604,7 @@ static int dump_trigger(FILE *sql_file, MYSQL_RES *show_create_trigger_rs,
     switch_sql_mode(sql_file, ";", row[1]);

     if (opt_drop_trigger)
-      fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS %s */;\n", row[0]);
+      fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS `%s` */;\n", row[0]);

Чтобы собрать пропатченую версию надо поставить пару пакетов и на Ubuntu/Debian, запустить cmake с параметрами из документации к Перконе, а затем make‘ом собрать только mysqldump.

apt-get install build-essential cmake bison libaio-dev libncurses5-dev libreadline-dev
cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_CONFIG=mysql_release -DFEATURE_SET=community -DWITH_EMBEDDED_SERVER=OFF
make mysqldump

После компиляции получаем патченую версию mysqldump, в которой имена триггеров экранируются корректно. Можно дампить новым дампером:

./percona-server/client/mysqldump \
    --socket=/var/run/mysqld/mysqld.sock \
    -uroot -p --routines --events --triggers \
    --add-drop-trigger --quote-names \
    --no-create-info --no-data --no-create-db --skip-opt \
    database_name | sed -r 's/DEFINER[ ]*=[ ]*[^*]*\*/\*/' > dump.sql

Как я стал электронным гражданином Эстонии

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

Решение заполнить анкету появилось под влиянием хайпа, а увидев среди пунктов обоснований получения электронного гражданства пункт «Fan of e-Residency» заполнил все поля, оплатил и стал ждать.

Первое письмо получил ровно через месяц после подачи анкеты 18 июля. В письме сообщалось, что моя анкета принята к рассмотрению, а спустя еще месяц, 20 июля пришло сообщение о положительном решении.

e-Residency

И вот наконец спустя почти 3 месяца ожидания я получил в посольстве заветный и скромный конверт, содержащий в себе:

  • Непосредственно карточку с чипом;
  • Небольшой складной USB считывать;
  • Конверт с PIN/PUK кодами.

Наиболее любопытным из этого набора оказался USB считыватель карточки который из формата usb-флешки раскладывается в Т-образную конструкцию для подключения карточки к ноутбуку. 

e-Residency

Для работы с картой есть два убогих приложения. Одно из них установит сертификат в браузер, для идентификации и позволяет менять PIN коды. Второе приложение позволяет подписывать документы, шифровать любые файлы и получить доступ к почте в домене @eesti.ee.

Книги о путешествиях и не только

Подборка из пяти книг о путешествиях, которые я прочитал с начала 2015 года и советую тем, кто так же как и я не равнодушен к такого рода литературе.

«Пляж», Алекс Гарленд — столько раз путешествовал в Таиланд, а эта книга каждый раз ускользала из моего поля интересов. Теперь же спустя год после посещения района островов, описанных в книге, прочитал ее и не пожалел. Приятно сравнить современный Бангкок с видением города образца 1995 года.

«Tokyo on Foot», Florent Chavouet — красочный путеводитель (если это можно так назвать) французского художника по городу Токио. Переехав на шесть месяцев в Японию он каждый день делал зарисовки и заметки того, что происходило вокруг него. Каждую страницу этого путеводителя хочется разглядывать до мелочей.

«Самый большой дурак под солнцем», Кристоф Рехаге — книга на которую я случайно обратил внимание еще в Венгерском аэропорту, и купив которую я прочитал запоем меньше чем за неделю. Это увлекательная история о человеке совершившем путешествие пешком через Китай, и взгляд европейца на современный Китай, за пределами крупных городов и конечно это истории людей, которых он встречал на своем пути.

«Journeys of a Lifetime: 500 of the World’s Greatest Trips» — очень большая и красивая книга, определенно прекрасный источник вдохновения для будущих путешествий.

«Shoreditch Wild Life», Dougie Wallace — альбом фотографий весёлого и безумного лондонского квартала — Шордич. Именно здесь проходят самые долгие и шумные вечеринки, по улицам ходят фрики всех мастей, а стены домов пестрят работами именитых граффити-художников.

Raspberry Pi - Jake

Сегодня хотел бы рассказать о еще одной Raspberry Pi которая помимо медиацентра и ретро консоли у меня на хозяйстве. И если предыдущие Raspberry Pi однозадачные, то эта представляет собой песочницу для экспериментов.

Сегодня на малине стоит два сервиса для работы с файлами и сетью — Tor Socks5 proxy и сервис для хранения файлов — Owncloud. Далее кратко расскажу о том как я это настроил.

Tor Socks5 proxy

Raspberry Pi

Ситуаций когда необходим прокси-сервер много, к ним относятся сайты волею случая оказавшиеся на одном IP с сайтами из черного списка Роскомнадзора. А потому я привык, что рабочий прокси-сервер и VPN всегда под рукой. 

И если OpenVPN легко поднять за пару кликом в каждом втором VPS хостере, то установку Tor большинство облачных провайдеров строго не приветствует. Что ж, будем держать домашнюю ноду, попутно поучаствовав в Tor Challenge. Начнем с установки необходимых пакетов для Raspbian:

sudo apt-get update
sudo apt-get install tor tor-arm

Правим файл /etc/tor/torrc для работы в режимах Relay и Socks5 proxy:

SocksPort 192.168.1.20:9050 # тут IP Raspberry Pi
SocksPolicy accept 192.168.1.1/24 # домашная подсеть 0-255
SocksPolicy reject *
Log notice file /var/log/tor/notices.log
RunAsDaemon 1
DataDirectory /var/lib/tor
ORPort 9001
RelayBandwidthRate 100 KB  # Мягкое ограничение Relay трафика
RelayBandwidthBurst 200 KB # Жесткое ограничение Relay трафика
ExitPolicy reject *:* # Не работать как exit-нода

Перезагружаем демона sudo /etc/init.d/tor restart и смотрим за трафиком через sudo -u debian-tor arm. Но я хотел чтобы Tor-Arm запускался при включении системы, поэтому внес еще несколько изменений:

# в файле `/etc/inittab` заменил
1:2345:respawn:/sbin/getty --noclear 38400 tty1
# на
1:2345:respawn:/bin/login -f pi tty1 </dev/tty1 >/dev/tty1 2>&1

# в файле `/etc/profile` добавил в конeц
[ -n "${SSH_CONNECTION}" ] || sudo -u debian-tor arm

Теперь в любимом браузере при необходимости подключения к TOR устанавливаем параметры Socks5 прокси и вперед.

Raspberry Pi

OwncloudPie

Второй эксперимент — это установка популярного сервиса Owncloud на малину. Скрипт установки называется OwncloudPie, от автора другого успешного проекта — RetroPie. Для установки нужно установить всего несколько пакетов и запустить скрипт, который сам сделает остальную работу:

sudo apt-get install -y git dialog
sudo ./owncloudpie_setup.sh

В ввиду маломощности Raspberry Pi выбираем NGiNX based установку, а перед этим в меню выбираем Set server URL и меняем значение на _, чтобы не привязываться к определенному servername‘у. После завершения установки заходим на работающий сервис https://192.168.1.20/owncloud через браузер.

Работает все не быстро и я себе слабо представляю как все будет грузиться когда размер базы перевалит 1GB. А пока эксперименты продолжаются.

Скачать все из CloudApp

Некогда прекрасный сервис CloudApp в очередной раз решил монетизироваться и прислал письмо с сообщением о том, что со 2 апреля месячный лимит на загрузку составляет всего 10 файлов.

Your CloudApp account is changing on April 2. Most importantly, all free accounts will have a drop limit of 10 drops per month.

Переходить на платную версию смысла нет, цены у них не самые низкие на рынке. В связи с этим начинаю поиск альтернатив, требований не много но они есть. А именно: иметь вменяемое API, клиент под OSX, возможность использовать собственный домен и адекватные цены.

А пока CloudApp не придумали очередной эксперимент по ограничению, решил вытянуть весь архив файлов которые когда-либо загружал через них. Для этих целей написал небольшой скрипт на Python, который скачивает все файлы с оригинальными названиями. Забрать можно на GIST.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import time
import json
import urllib
import urllib2
from dateutil.parser import parse

USERNAME = "mail@gmail.com"
PASSWORD = "superpass"


def request(url):
    # Digest Authentication
    authhandler = urllib2.HTTPDigestAuthHandler()
    authhandler.add_password('Application', url, USERNAME, PASSWORD)
    opener = urllib2.build_opener(authhandler)
    opener.addheaders = [('Accept', 'application/json')]
    urllib2.install_opener(opener)

    response = urllib2.urlopen(url)
    if response.getcode() == 200:
        body = json.loads(response.read())
        if len(body):
            return body

    return False


def download(url, created_at):
    if not url:
        return False

    try:
        print 'Source: %s' % url
        filename = url.split('/')[-1]
        filename = urllib.unquote(filename.encode('utf-8')).decode('utf-8')

        # if a file exists add created_at
        if os.path.isfile(filename):
            filename = '%s-%s' % (created_at, filename)

        print 'Downloading... %s' % filename
        urllib.urlretrieve(url, filename)
        os.utime(filename, (created_at, created_at))
    except IOError:
        download(url)
    except LookupError:
        pass


if __name__ == "__main__":
    page = 0
    result = True
    while result:
        page = page + 1
        result = request('http://my.cl.ly/items?per_page=100&page=' + str(page))

        if not result:
            break

        for n in result:
            source_url = n.get(u'source_url')
            created_at = int(time.mktime(parse(n.get(u'updated_at')).timetuple()))
            # print json.dumps(n, sort_keys=True, indent=4)
            download(source_url, created_at)

Linux From Scratch

Уже долгое время у меня в iPad’е лежала книжка Герарда Бикманса — Linux From Scratch, которую я никак не решался начинать читать. И вот спустя два безумно скучных дня, я получил заветные буквы в терминале:

> uname -a
> Linux <lfs> 3.19.0 #1 SMP Wed Mar 11 12:21 18 MSK 2015 i686 GNU/Linux

О чем эта книга по описанию? Как не сложно догадаться, это книга о том, как собрать работающую операционную систему GNU/Linux из исходных кодов. Звучит очень захватывающе и многообещающе.

О чем на самом деле эта книга? Все просто, эта книга ничего не потеряла бы от переименования в «1000 и 1 способ выполнить make». Сборка по книге состоит, чуть менее чем полностью, из комбинации команд tar, configure, make, cp и cat. И как правило именно в такой последовательности.

К сборке я подошел самым банальным образом и делал это на свежеустановленной Ubuntu 12.04 через Vagrant, которую даже не пришлось особым образом готовить, за исключением сборки кросс-компайлера GCC и косяков с GRUB’ом не получил не одной фатальной ошибки.

Чему должна была научить эта книга, видимо архитектуре операционной системы, но вместо этого она распыляется на описание флагов сборки без предоставления читателю выбора что-то решить самому. За всю книгу единственный выбор который читателю предложили, это выбор альтернативного редактора вместо vim, на этом решили и закончить самостоятельность. Спасибо, на этом можно закончить rm -rf /mnt/lfs.