Как следить за перенаправлением HTTP в Node.js?

86

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

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-ответов от узлового приложения?

Зак
источник

Ответы:

46

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

request

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

Райнос
источник
20
Почему живой b'jesus не является частью встроенного модуля http ?!
aaaidan
1
Это. Называется http.requestэто API довольно просто.
Raynos
3
Можно ли каким-то образом иметь обратный вызов для каждого перенаправления? Я хотел бы сохранить каждый URL-адрес, по которому проходит запрос. Не удалось найти в документации.
Ignas
13
@Raynos, метод request () встроенного httpмодуля не следует перенаправлениям, поэтому он не является частью встроенного httpмодуля.
gilad mayani
4
requestустарела.
Иван Рубинсон
111

Если все, что вы хотите сделать, это следовать перенаправлениям, но по-прежнему хотите использовать встроенные модули 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';

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

Оливье Лалонде
источник
Это сейчас здесь: github.com/request/request/blob/…
Адриан Линч,
1
Это намного лучше, чем принятый ответ, в requestкотором добавлено 20+ новых зависимостей в ваш модуль для такой простой задачи. Спасибо за облегчение модулей npm, Оливер! :)
Sainan
Не работает, когда я использую его со звуком, надежно размещенным на s3.
thedreamsaver
Используя TypeScript, добавьте это к вашей установке npm: npm install @ types / follow-redirects, чтобы вы могли использовать импорт {https} из 'follow-redirects'; Это фантастический, простой и очень эффективный модуль. Мерси Оливье!
Луи-Эрик Симар,
26

Обновить:

Теперь вы можете отслеживать все перенаправления с var request = require('request');помощью параметра followAllRedirectsparam.

request({
  followAllRedirects: true,
  url: url
}, function (error, response, body) {
  if (!error) {
    console.log(response);
  }
});
skozz
источник
это полный код? ваш var есть, httpно тогда вы используете функцию под названиемrequest
jcollum
7
вы не можете просто потребовать ('запрос'), это внешний модуль, и его нужно сначала загрузить и установить - npmjs.com/package/request (запрос на установку npm)
gilad mayani
requestустарел
Росс Макартур
18

Сделайте еще один запрос на основе 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);
Накилон
источник
Это ответ, если вы хотите использовать встроенную http lib, следуйтеresponse.headers.location
Vidar
6

Вот функция, которую я использую для получения 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, но следует перенаправлению.

jcubic
источник
2

В случае запроса 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);
        }
     });
}
Санджит Кумар
источник
1

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

import https from "https";

function get(url, resolve, reject) {
  https.get(url, (res) => {
    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));

Wiesson
источник
0

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

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

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

Кевин Ф.
источник
Это очень частный пример перенаправления, который ничего не сделает, скажем, с tinyurls или bit.ly ссылками.
Дэн Даскалеску
-1

Возможно, здесь что-то вроде поста некромантии, но ...

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

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

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

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));
несинхронизированный
источник