Легковесный модуль для HTTP запросов

Все началось с того, что передо мной встала задача написать бота для 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
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 29

    –8
    Перечитайте второй абзац)
      +2
      Спасибо, поправил :)
        –3
        Пожалуйста)
        П.С. Во втором абзаце было дублирование, автор уже убрал. Минусы можно больше не ставить :D
        Круто, еще и в карму кто то плюнул О_о. У кого то среда не задалась)
          +8
          Об ошибках нужно писать в личку… т.к. после исправления статьи, ваш комментарий становится неактуальным.
            +1
            Спасибо, буду знать.
        0
        Что бы так активно ставили + в карму за статьи, как ее сливают за один комментарий с желанием помочь…
        Спасибо, хорошо мотивирует.
          0
          Каждую неделю на протяжении лет 4-5 вижу одну и ту же ситуацию: кто-то пишет в комментарии об опечатке в статье, ему ставят минусы, он начинает писать что-то типа «Ой-ой, за что минусы то? Вот беда то...». Новые сообщения только ещё минусов наберут. Просто не пишите в следующий раз в личку и всё.
        0
        А чем стандартный http не устроил, если так важна производительность?
          +3
          Если код на гитхабе посмотреть, то всем устроил. Автор немножко припудрил сахаром http и https, как по мне, так довольно мило получилось.
            +1
            Громоздкостью использования.
            0
            Так как же теперь с потреблением памяти? Без заключительного абзаца неясно стоила ли овчинка выделки. Желательно с цифрами. Спасибо.
              +1
              Потребление памяти уменьшилось ( еще сильнее оно уменьшилось при использовании ручной сборки мусора )

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

              Request — { rss: 71733248, heapTotal: 57203968, heapUsed: 25940592 }
              tiny_request — { rss: 46379008, heapTotal: 47928576, heapUsed: 11682480 }
                0
                #Нихренасе# потребление 47-57 метров 300 запросов?
                  0
                  Я думаю, что тут приведены числа для всего приложения. Если так, то стоит обратить внимание только на разности чисел.
              0
              Посмотрите в сторону functional programming (например rxjs), чтобы избавиться от бойлерплейта 'if (!err && response.statusCode == 200) {'
              Я бы предпочел видеть API в таком виде:
              req
                .post(...)
                .onOk((body, response) -> console.log(body))
                .onStatus(404, () -> ...)
              
                0
                Знаете про superagent?
                  0
                  Нет, я больше по Scala-стеку. :)
                  +3
                  А кто-то предпочёл бы увидеть следующее:
                  req.post(...).then(...)
                    0
                    А кто-то и подавно

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


                    :)
                      0
                      co + promises
                        0
                        Именно их и использовал долгое время, но они все равно создают лишний шум в коде, так что теперь babel «es7.asyncFunctions» + promises — наше все.
                          0
                          bluebird.coroutine быстрее ;)
                    +1
                    А чем не понравился, например, got?
                      0
                      > Dependencies (13)

                      Слишком много всего для такого функционала, я думаю.
                      0
                      Зачем писать в 2015 без Promise API? Возьмите node-fetch.
                        –3
                        А вы думаете, что будущее за Promise API? Мое мнение, что нет и даже не за асинхронной архитектурой. Недавно было интересно тестануть nodejs на потребление памяти при постоянной нагрузки, и честно говоря классическая связка php/nginx — выигрывает в разы по производительности и по потреблению памяти. Nodejs использую только как commit-server не более того.

                        PS: было бы хорошо если бы кто-нибуть написал статью со статистикой потребления памяти при нагрузках с использованием разных сервисов (http,https,ssh и т.д.)
                          0
                          Если php+nginx выигрывает в разы, то вы что-то делаете не так. Априори, пересоздаваемое окружение на каждый запрос вместе с блокировкой ввода-вывода будет медленнее, чем горячая точка входа и неблокирующее I/O. Ну а асинхронная архитектура имеет и плюсы, и минусы. Например, в том же сравнении php+nginx и node.js (если использовать везде 1 процесс) node.js будет быстрее, так как не будет блокировки из-за I/O.
                            0
                            Про PS вообще не понял что вы имеете ввиду. Потребление памяти не зависит от языка, на котором решается задача. Это зависит только от задачи и от качества написанного кода.
                          +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, а тут ее парсить и потом Вы ее опять склеите же.

                          Only users with full accounts can post comments. Log in, please.