Как вы следуете перенаправлению HTTP в Node.js?

Я хочу открыть страницу в узле и обработать содержимое в своем приложении. Что-то вроде этого работает хорошо:

var opts = {host: host, path:pathname, port: 80};
http.get(opts, function(res) {
  var page = '';
  res.on('data', function (chunk) {
    page += chunk;
  });
  res.on('end', function() {
     // process page
  });

Однако это не работает, если страница возвращает перенаправление 301/302. Как бы я сделал это повторно используемым способом в случае нескольких перенаправлений? Есть ли модуль-оболочка поверх http, чтобы упростить обработку http-ответов от приложения node?


person Zach    schedule 06.09.2011    source источник


Ответы (9)


Есть ли модуль-оболочка поверх http, чтобы упростить обработку http-ответов от приложения node?

request

Логика перенаправления в запросе

person Raynos    schedule 06.09.2011
comment
Почему живой б'хесус не является частью встроенного http-модуля?! - person aaaidan; 23.10.2012
comment
Это. Он называется http.request API довольно простой. - person Raynos; 24.10.2012
comment
Можно ли каким-то образом иметь обратный вызов для каждого перенаправления? Я хотел бы сохранить каждый URL-адрес, через который проходит запрос. В документах не нашел. - person Ignas; 26.06.2013
comment
@Raynos, метод request() встроенного модуля http не следует перенаправлениям, поэтому он не является частью встроенного модуля http. - person gilad mayani; 10.11.2016
comment
Это должен быть лучший ответ, поскольку он работает как замена встроенному клиенту узла: stackoverflow.com/a/13390241/ 589493 - person Tchakabam; 15.10.2018
comment
request — довольно раздутый модуль по сегодняшним меркам. Люди предпочитают перекрестную выборку, которая использует родной Fetch, если он запущен в контексте браузера, и переносит тот же API на Node. - person Dan Dascalescu; 25.12.2018
comment
request устарел. - person Ivan Rubinson; 27.04.2020
comment
устарело действительно - person Klesun; 17.06.2020

Если все, что вы хотите сделать, это следовать перенаправлениям, но по-прежнему хотите использовать встроенные модули HTTP и HTTPS, я предлагаю вам использовать https://github.com/follow-redirects/follow-redirects.

yarn add follow-redirects
npm install follow-redirects

Все, что вам нужно сделать, это заменить:

var http = require('http');

с участием

var http = require('follow-redirects').http;

... и все ваши запросы будут автоматически следовать редиректам.

С TypeScript вы также можете установить типы

npm install @types/follow-redirects

а затем использовать

import { http, https } from 'follow-redirects';

Раскрытие информации: я написал этот модуль.

person Olivier Lalonde    schedule 15.11.2012
comment
Теперь это здесь: github.com/request/request/blob / - person Adrian Lynch; 09.03.2016
comment
Это намного лучше, чем принятый ответ с request, который добавит более 20 новых зависимостей в ваш модуль для такой простой задачи. Спасибо, Оливер, за облегчение модулей npm! :) - person Sainan; 16.01.2019
comment
Не работает, когда я использую его с аудио, безопасно размещенным на s3. - person thedreamsaver; 18.05.2019
comment
Используя TypeScript, добавьте это в свою установку npm: npm install @types/follow-redirects, чтобы вы могли использовать import {https} from 'follow-redirects'; Это фантастический, простой и очень эффективный модуль. Мерси Оливье! - person Louis-Eric Simard; 26.10.2019

Обновление:

Теперь вы можете следить за всеми переадресациями с помощью var request = require('request');, используя параметр followAllRedirects.

request({
  followAllRedirects: true,
  url: url
}, function (error, response, body) {
  if (!error) {
    console.log(response);
  }
});
person skozz    schedule 12.04.2015
comment
это полный код? ваша переменная http, но затем вы используете функцию с именем request - person jcollum; 23.06.2015
comment
вы не можете просто требовать ('запрос'), это внешний модуль, который необходимо сначала загрузить и установить - npmjs.com/package/request (запрос на установку npm) - person gilad mayani; 10.11.2016
comment
request устарело - person Ross MacArthur; 02.05.2020

Сделайте еще один запрос на основе response.headers.location:

      const request = function(url) {
        lib.get(url, (response) => {
          var body = [];
          if (response.statusCode == 302) {
            body = [];
            request(response.headers.location);
          } else {
            response.on("data", /*...*/);
            response.on("end", /*...*/);
          };
        } ).on("error", /*...*/);
      };
      request(url);
person Nakilon    schedule 20.08.2017
comment
Это ответ, если вы хотите использовать встроенную http-библиотеку, следуйте response.headers.location - person Vidar; 23.07.2019

Вот мой (рекурсивный) подход к загрузке JSON с простым узлом, пакеты не требуются.

import https from "https";

function get(url, resolve, reject) {
  https.get(url, (res) => {
    // if any other status codes are returned, those needed to be added here
    if(res.statusCode === 301 || res.statusCode === 302) {
      return get(res.headers.location, resolve, reject)
    }

    let body = [];

    res.on("data", (chunk) => {
      body.push(chunk);
    });

    res.on("end", () => {
      try {
        // remove JSON.parse(...) for plain data
        resolve(JSON.parse(Buffer.concat(body).toString()));
      } catch (err) {
        reject(err);
      }
    });
  });
}

async function getData(url) {
  return new Promise((resolve, reject) => get(url, resolve, reject));
}

// call
getData("some-url-with-redirect").then((r) => console.log(r));

person wiesson    schedule 26.06.2020

Вот функция, которую я использую для получения URL-адреса с перенаправлением:

const http = require('http');
const url = require('url');

function get({path, host}, callback) {
    http.get({
        path,
        host
    }, function(response) {
        if (response.headers.location) {    
            var loc = response.headers.location;
            if (loc.match(/^http/)) {
                loc = new Url(loc);
                host = loc.host;
                path = loc.path;
            } else {
                path = loc;
            }
            get({host, path}, callback);
        } else {
            callback(response);
        }
    });
}

он работает так же, как http.get, но следует перенаправлению.

person jcubic    schedule 12.01.2019

В случае запроса PUT или POST. если вы получаете код состояния 405 или метод не разрешен. Попробуйте эту реализацию с библиотекой request и добавьте указанные свойства.
followAllRedirects: true,
followOriginalHttpMethod: true

       const options = {
           headers: {
               Authorization: TOKEN,
               'Content-Type': 'application/json',
               'Accept': 'application/json'
           },
           url: `https://${url}`,
           json: true,
           body: payload,
           followAllRedirects: true,
           followOriginalHttpMethod: true
       }

       console.log('DEBUG: API call', JSON.stringify(options));
       request(options, function (error, response, body) {
       if (!error) {
        console.log(response);
        }
     });
}
person Sanjeet kumar    schedule 08.07.2019

Если у вас есть сервер https, измените URL-адрес, чтобы использовать протокол https://.

У меня возникла аналогичная проблема с этим. Мой URL-адрес имеет протокол http://, и я хочу сделать запрос POST, но сервер хочет перенаправить его на https. Что происходит, так это то, что поведение узла http отправляет запрос на перенаправление (далее) в методе GET, что не так.

Что я сделал, так это изменил свой URL-адрес на протокол https://, и он работает.

person Kevin F.    schedule 26.06.2017
comment
Это очень специфический пример перенаправления, и он ничего не сделает, скажем, для ссылок tinyurls или bit.ly. - person Dan Dascalescu; 26.12.2018

Возможно, здесь немного некромантский пост, но...

вот функция, которая выполняет до 10 перенаправлений и обнаруживает бесконечные циклы перенаправления. также анализирует результат в JSON

Примечание: используется помощник обратного вызова (показан в конце этого поста)

( TLDR; полная рабочая демонстрация в контексте здесь или ремикс-версия здесь)

function getJSON(url,cb){

    var callback=errBack(cb);
    //var callback=errBack(cb,undefined,false);//replace previous line with this to turn off logging

    if (typeof url!=='string') {
        return callback.error("getJSON:expecting url as string");
    }

    if (typeof cb!=='function') {
        return callback.error("getJSON:expecting cb as function");
    }

    var redirs = [url],
    fetch = function(u){
        callback.info("hitting:"+u);
        https.get(u, function(res){
            var body = [];
            callback.info({statusCode:res.statusCode});
            if ([301,302].indexOf(res.statusCode)>=0) {
                if (redirs.length>10) {
                    return callback.error("excessive 301/302 redirects detected");
                } else {
                    if (redirs.indexOf(res.headers.location)<0) {
                        redirs.push(res.headers.location);
                        return fetch(res.headers.location);
                    } else {
                        return callback.error("301/302 redirect loop detected");
                    }
                }
            } else {
              res.on('data', function(chunk){
                  body.push(chunk);
                  callback.info({onData:{chunkSize:chunk.length,chunks:body.length}});
              });
              res.on('end', function(){
                  try {
                      // convert to a single buffer
                      var json = Buffer.concat(body);
                      console.info({onEnd:{chunks:body.length,bodyLength:body.length}});

                      // parse the buffer as json
                      return callback.result(JSON.parse(json),json);
                  } catch (err) {

                      console.error("exception in getJSON.fetch:",err.message||err);

                      if (json.length>32) {
                        console.error("json==>|"+json.toString('utf-8').substr(0,32)+"|<=== ... (+"+(json.length-32)+" more bytes of json)");
                      } else {
                          console.error("json==>|"+json.toString('utf-8')+"|<=== json");
                      }

                      return callback.error(err,undefined,json);
                  }
              });
            }
        });
    };
    fetch(url);   
}

Примечание: используется помощник обратного вызова (показан ниже)

вы можете вставить это в консоль узла, и он должен работать как есть.

(или полную рабочую демонстрацию в контексте см. здесь)

var 

fs      = require('fs'),
https   = require('https');

function errBack (cb,THIS,logger) {

   var 
   self,
   EB=function(fn,r,e){
       if (logger===false) {
           fn.log=fn.info=fn.warn=fn.errlog=function(){};       
       } else {
           fn.log        = logger?logger.log   : console.log.bind(console);
           fn.info       = logger?logger.info  : console.info.bind(console);
           fn.warn       = logger?logger.warn  : console.warn.bind(console);
           fn.errlog     = logger?logger.error : console.error.bind(console);
       }
       fn.result=r;
       fn.error=e;
       return (self=fn);
   };


   if (typeof cb==='function') {
       return EB(

            logger===false // optimization when not logging - don't log errors
            ?   function(err){
                   if (err) {
                      cb (err);
                     return true;
                   }
                   return false;
               }

            :  function(err){
                   if (err) {
                      self.errlog(err);
                      cb (err);
                     return true;
                   }
                   return false;
               },

           function () {
               return cb.apply (THIS,Array.prototype.concat.apply([undefined],arguments));
           },
           function (err) {
               return cb.apply (THIS,Array.prototype.concat.apply([typeof err==='string'?new Error(err):err],arguments));
           }
       );
   } else {

       return EB(

           function(err){
               if (err) {
                   if (typeof err ==='object' && err instanceof Error) {
                       throw err;
                   } else {
                       throw new Error(err);
                   }
                   return true;//redundant due to throw, but anyway.
               }
               return false;
           },

           logger===false
              ? self.log //optimization :resolves to noop when logger==false
              : function () {
                   self.info("ignoring returned arguments:",Array.prototype.concat.apply([],arguments));
           },

           function (err) {
               throw typeof err==='string'?new Error(err):err;
           }
       );
   }
}

function getJSON(url,cb){

    var callback=errBack(cb);

    if (typeof url!=='string') {
        return callback.error("getJSON:expecting url as string");
    }

    if (typeof cb!=='function') {
        return callback.error("getJSON:expecting cb as function");
    }

    var redirs = [url],
    fetch = function(u){
        callback.info("hitting:"+u);
        https.get(u, function(res){
            var body = [];
            callback.info({statusCode:res.statusCode});
            if ([301,302].indexOf(res.statusCode)>=0) {
                if (redirs.length>10) {
                    return callback.error("excessive 302 redirects detected");
                } else {
                    if (redirs.indexOf(res.headers.location)<0) {
                        redirs.push(res.headers.location);
                        return fetch(res.headers.location);
                    } else {
                        return callback.error("302 redirect loop detected");
                    }
                }
            } else {
              res.on('data', function(chunk){
                  body.push(chunk);
                  console.info({onData:{chunkSize:chunk.length,chunks:body.length}});
              });
              res.on('end', function(){
                  try {
                      // convert to a single buffer
                      var json = Buffer.concat(body);
                      callback.info({onEnd:{chunks:body.length,bodyLength:body.length}});

                      // parse the buffer as json
                      return callback.result(JSON.parse(json),json);
                  } catch (err) {
                      // read with "bypass refetch" option
                      console.error("exception in getJSON.fetch:",err.message||err);

                      if (json.length>32) {
                        console.error("json==>|"+json.toString('utf-8').substr(0,32)+"|<=== ... (+"+(json.length-32)+" more bytes of json)");
                      } else {
                          console.error("json==>|"+json.toString('utf-8')+"|<=== json");
                      }

                      return callback.error(err,undefined,json);
                  }
              });
            }
        });
    };
    fetch(url);   
}

var TLDs,TLDs_fallback = "com.org.tech.net.biz.info.code.ac.ad.ae.af.ag.ai.al.am.ao.aq.ar.as.at.au.aw.ax.az.ba.bb.bd.be.bf.bg.bh.bi.bj.bm.bn.bo.br.bs.bt.bv.bw.by.bz.ca.cc.cd.cf.cg.ch.ci.ck.cl.cm.cn.co.cr.cu.cv.cw.cx.cy.cz.de.dj.dk.dm.do.dz.ec.ee.eg.er.es.et.eu.fi.fj.fk.fm.fo.fr.ga.gb.gd.ge.gf.gg.gh.gi.gl.gm.gn.gp.gq.gr.gs.gt.gu.gw.gy.hk.hm.hn.hr.ht.hu.id.ie.il.im.in.io.iq.ir.is.it.je.jm.jo.jp.ke.kg.kh.ki.km.kn.kp.kr.kw.ky.kz.la.lb.lc.li.lk.lr.ls.lt.lu.lv.ly.ma.mc.md.me.mg.mh.mk.ml.mm.mn.mo.mp.mq.mr.ms.mt.mu.mv.mw.mx.my.mz.na.nc.ne.nf.ng.ni.nl.no.np.nr.nu.nz.om.pa.pe.pf.pg.ph.pk.pl.pm.pn.pr.ps.pt.pw.py.qa.re.ro.rs.ru.rw.sa.sb.sc.sd.se.sg.sh.si.sj.sk.sl.sm.sn.so.sr.st.su.sv.sx.sy.sz.tc.td.tf.tg.th.tj.tk.tl.tm.tn.to.tr.tt.tv.tw.tz.ua.ug.uk.us.uy.uz.va.vc.ve.vg.vi.vn.vu.wf.ws.ye.yt.za.zm.zw".split(".");
var TLD_url = "https://gitcdn.xyz/repo/umpirsky/tld-list/master/data/en/tld.json";
var TLD_cache = "./tld.json";
var TLD_refresh_msec = 15 * 24 * 60 * 60 * 1000;
var TLD_last_msec;
var TLD_default_filter=function(dom){return dom.substr(0,3)!="xn-"};


function getTLDs(cb,filter_func){

    if (typeof cb!=='function') return TLDs;

    var 
    read,fetch,
    CB_WRAP=function(tlds){
        return cb(
            filter_func===false
            ? cb(tlds)
            : tlds.filter(
                typeof filter_func==='function'
                 ? filter_func
                 : TLD_default_filter)
            );
    },
    check_mtime = function(mtime) {
       if (Date.now()-mtime > TLD_refresh_msec) {
           return fetch();
       } 
       if (TLDs) return CB_WRAP (TLDs);
       return read();
    };

    fetch = function(){

        getJSON(TLD_url,function(err,data){
            if (err) {
                console.log("exception in getTLDs.fetch:",err.message||err);
                return read(true);      
            } else {
                TLDs=Object.keys(data);

                fs.writeFile(TLD_cache,JSON.stringify(TLDs),function(err){
                    if (err) {
                        // ignore save error, we have the data
                        CB_WRAP(TLDs);
                    } else {
                        // get mmtime for the file we just made
                        fs.stat(TLD_cache,function(err,stats){
                            if (!err && stats) {
                               TLD_last_msec = stats.mtimeMs; 
                            }
                            CB_WRAP(TLDs);    
                        });
                    }
                });
            }
        });
    };

    read=function(bypassFetch) {
        fs.readFile(TLD_cache,'utf-8',function(err,json){

            try {
                if (err) {

                    if (bypassFetch) {
                        // after a http errror, we fallback to hardcoded basic list of tlds
                        // if the disk file is not readable
                        console.log("exception in getTLDs.read.bypassFetch:",err.messsage||err);    

                        throw err;
                    }
                    // if the disk read failed, get the data from the CDN server instead
                    return fetch();
                }

                TLDs=JSON.parse(json);
                if (bypassFetch) {
                    // we need to update stats here as fetch called us directly
                    // instead of being called by check_mtime
                    return fs.stat(TLD_cache,function(err,stats){
                        if (err) return fetch();
                        TLD_last_msec =stats.mtimeMs;
                        return CB_WRAP(TLDs);
                    });
                }

            } catch (e){
                // after JSON error, if we aren't in an http fail situation, refetch from cdn server
                if (!bypassFetch) {
                    return fetch();
                }

                // after a http,disk,or json parse error, we fallback to hardcoded basic list of tlds

                console.log("exception in getTLDs.read:",err.messsage||err);    
                TLDs=TLDs_fallback;
            }

            return CB_WRAP(TLDs);
        });
    };

    if (TLD_last_msec) {
        return check_mtime(TLD_last_msec);
    } else {
        fs.stat(TLD_cache,function(err,stats){
            if (err) return fetch();
            TLD_last_msec =stats.mtimeMs;
            return check_mtime(TLD_last_msec);
        });
    }
}

getTLDs(console.log.bind(console));
person unsynchronized    schedule 23.04.2019