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

Бот был написан. Я заметил, что потребление им памяти росло с каждым запросом к API, уличив в проблеме тяжеловесный Request, я решил попробовать написать свой модуль для HTTP запросов, максимально простой, легковесный и быстрый.



В итоге вышел максимально компактный (сейчас в основном файле модуля меньше 200 строк) и не обделенный функционалом модуль, который я назвал tiny_request.

Простота использования


Для обычного GET запроса достаточно написать всего несколько строк:
var req = require('tiny_request')

req.get('http://google.com', function(body, response, err){
    if (!err && response.statusCode == 200) {  
        console.log(body) 
    } 
})


JSON


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

var req = require('tiny_request')

req.get({ url: 'http://test.com/json', json: true}, function(body, response, err){
    if (!err && response.statusCode == 200) {  
        console.log(body) //body now is parsed JSON object
    } 
})


GET запросы


Для запроса с GET параметрами достаточно передать query равный объекту с GET параметрами, также для изменения порта запроса достаточно передать параметр port:
req.get({ url: 'http://test.com', query: { test: 'test' }, port: 8080}, function(body, response, err){
    if (!err && response.statusCode == 200) {  
        console.log(body) 
    } 
})


POST Multipart


Куда же без POST запросов и передачи файлов?
var data = {
    image: {
        value: fs.createReadStream('photo.png'), 
        filename: 'photo.png',
        contentType: 'image/png'            
    },
    test: 'test'
}

req.post({
    url: 'http://test.com',
    multipart: data 
}, function(body, response, err){
    if (!err && response.statusCode == 200) {  
        console.log(body) 
    } 
})


POST формы


Работа с формами так же очень проста:

var form = {
    test: 'test'
}

req.post({ url: 'http://test.com', form: form}, function(body, response, err){
    if (!err && response.statusCode == 200) {  
        console.log(body) 
    } 
})


HTTP заголовки


Для добавления заголовков достаточно передать параметр headers:

var headers = {
    'Test-Header': 'test'
}

req.post({ url: 'http://test.com', headers: headers}, function(body, response, err){
    if (!err && response.statusCode == 200) {  
        console.log(body) 
    } 
})


Pipe stream


Работа со стримами тоже проста:

req.get({url: url, pipe: stream}) 


Все исходники можно найти на GitHub: github.com/Naltox/tiny_request

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


  1. Drag13
    12.08.2015 11:36
    -8

    Перечитайте второй абзац)


    1. Altox
      12.08.2015 11:51
      +2

      Спасибо, поправил :)


      1. Drag13
        12.08.2015 12:09
        -3

        Пожалуйста)
        П.С. Во втором абзаце было дублирование, автор уже убрал. Минусы можно больше не ставить :D
        Круто, еще и в карму кто то плюнул О_о. У кого то среда не задалась)


        1. BuriK666
          12.08.2015 12:18
          +8

          Об ошибках нужно писать в личку… т.к. после исправления статьи, ваш комментарий становится неактуальным.


          1. Drag13
            12.08.2015 12:20
            +1

            Спасибо, буду знать.


    1. Drag13
      12.08.2015 14:51

      Что бы так активно ставили + в карму за статьи, как ее сливают за один комментарий с желанием помочь…
      Спасибо, хорошо мотивирует.


      1. ShpuntiK
        19.08.2015 08:48

        Каждую неделю на протяжении лет 4-5 вижу одну и ту же ситуацию: кто-то пишет в комментарии об опечатке в статье, ему ставят минусы, он начинает писать что-то типа «Ой-ой, за что минусы то? Вот беда то...». Новые сообщения только ещё минусов наберут. Просто не пишите в следующий раз в личку и всё.


  1. ad3w
    12.08.2015 12:15

    А чем стандартный http не устроил, если так важна производительность?


    1. corvette
      12.08.2015 12:35
      +3

      Если код на гитхабе посмотреть, то всем устроил. Автор немножко припудрил сахаром http и https, как по мне, так довольно мило получилось.


    1. Altox
      12.08.2015 12:40
      +1

      Громоздкостью использования.


  1. northicewind
    12.08.2015 12:15

    Так как же теперь с потреблением памяти? Без заключительного абзаца неясно стоила ли овчинка выделки. Желательно с цифрами. Спасибо.


    1. Altox
      12.08.2015 12:44
      +1

      Потребление памяти уменьшилось ( еще сильнее оно уменьшилось при использовании ручной сборки мусора )

      Сейчас провел маленький тест — 300 запросов на habrahabr.ru
      Вот числа:

      Request — { rss: 71733248, heapTotal: 57203968, heapUsed: 25940592 }
      tiny_request — { rss: 46379008, heapTotal: 47928576, heapUsed: 11682480 }


      1. MaxFactor
        12.08.2015 15:34

        #Нихренасе# потребление 47-57 метров 300 запросов?


        1. keksmen
          12.08.2015 17:58

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


  1. aparamonov
    12.08.2015 13:10

    Посмотрите в сторону functional programming (например rxjs), чтобы избавиться от бойлерплейта 'if (!err && response.statusCode == 200) {'
    Я бы предпочел видеть API в таком виде:

    req
      .post(...)
      .onOk((body, response) -> console.log(body))
      .onStatus(404, () -> ...)
    


    1. Kinday
      12.08.2015 13:40

      Знаете про superagent?


      1. aparamonov
        12.08.2015 14:41

        Нет, я больше по Scala-стеку. :)


    1. AndyGrom
      12.08.2015 14:03
      +3

      А кто-то предпочёл бы увидеть следующее:
      req.post(...).then(...)


      1. VasilioRuzanni
        12.08.2015 14:11

        А кто-то и подавно

        let response = await req.post(...);
        
        if (response.statusCode === 404) {
          ...
        }
        
        console.log(response.body);
        
        ...
        


        :)


        1. inook
          12.08.2015 14:18

          co + promises


          1. VasilioRuzanni
            12.08.2015 14:41

            Именно их и использовал долгое время, но они все равно создают лишний шум в коде, так что теперь babel «es7.asyncFunctions» + promises — наше все.


          1. hell0w0rd
            12.08.2015 17:39

            bluebird.coroutine быстрее ;)


  1. Yavanosta
    12.08.2015 15:37
    +1

    А чем не понравился, например, got?


    1. Altox
      12.08.2015 15:44

      > Dependencies (13)

      Слишком много всего для такого функционала, я думаю.


  1. b1rdex
    12.08.2015 19:15

    Зачем писать в 2015 без Promise API? Возьмите node-fetch.


    1. MaxFactor
      12.08.2015 19:55
      -3

      А вы думаете, что будущее за Promise API? Мое мнение, что нет и даже не за асинхронной архитектурой. Недавно было интересно тестануть nodejs на потребление памяти при постоянной нагрузки, и честно говоря классическая связка php/nginx — выигрывает в разы по производительности и по потреблению памяти. Nodejs использую только как commit-server не более того.

      PS: было бы хорошо если бы кто-нибуть написал статью со статистикой потребления памяти при нагрузках с использованием разных сервисов (http,https,ssh и т.д.)


      1. b1rdex
        12.08.2015 19:59

        Если php+nginx выигрывает в разы, то вы что-то делаете не так. Априори, пересоздаваемое окружение на каждый запрос вместе с блокировкой ввода-вывода будет медленнее, чем горячая точка входа и неблокирующее I/O. Ну а асинхронная архитектура имеет и плюсы, и минусы. Например, в том же сравнении php+nginx и node.js (если использовать везде 1 процесс) node.js будет быстрее, так как не будет блокировки из-за I/O.


      1. b1rdex
        12.08.2015 20:01

        Про PS вообще не понял что вы имеете ввиду. Потребление памяти не зависит от языка, на котором решается задача. Это зависит только от задачи и от качества написанного кода.


  1. MarcusAurelius
    13.08.2015 13:30
    +1

    1. Делать err третьим параметром в callback, мягко говоря, не принято. Лучше ставить параметры в порядке убывания их важности и частоты использования: callback(err, response, body). Тут даже response важнее body, потому, что к response.statusCode точно обратятся, а к body только условно. Но это уже не так критично, главное err поставьте первым.
    2. Тип ответа JSON можно не задавать, а определять по Content-Type: application/json или application/javascript (если поддерживать JSONP). А функции для парса данных разных типов можно примешивать к response. Например, так выглядит компактнее:

    req.get('http://test.com/json, function(err, res) {
      if (!err && res.statusCode === 200) {
        // можем брать res.asJSON()
        // можем брать res.asBuffer() или res.asString()
      }
    });
    

    3. Иметь параметры для всего именованные параметры хорошо, но альтернативно задавать все в URL намного компактнее:
    Вместо: req.get({ url: 'http://test.com', query: { test: 'test' }, port: 8080}, callback);
    Делать: req.get('http://test.com:8080?test=test', callback);
    Да и часто все это у нас уже есть в виде одной строки URL, а тут ее парсить и потом Вы ее опять склеите же.