AngularJS: एक HTTP इंटरसेप्टर (परिपत्र निर्भरता) में सेवा को खारिज करना


118

मैं प्रमाणीकरण को संभालने के लिए अपने AngularJS ऐप के लिए एक HTTP इंटरसेप्टर लिखने की कोशिश कर रहा हूं।

यह कोड काम करता है, लेकिन मैं एक सेवा को मैन्युअल रूप से इंजेक्ट करने के बारे में चिंतित हूं क्योंकि मुझे लगा कि कोणीय इसे स्वचालित रूप से संभालने वाला है:

    app.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.interceptors.push(function ($location, $injector) {
        return {
            'request': function (config) {
                //injected manually to get around circular dependency problem.
                var AuthService = $injector.get('AuthService');
                console.log(AuthService);
                console.log('in request interceptor');
                if (!AuthService.isAuthenticated() && $location.path != '/login') {
                    console.log('user is not logged in.');
                    $location.path('/login');
                }
                return config;
            }
        };
    })
}]);

मैंने क्या करना शुरू किया, लेकिन परिपत्र निर्भरता की समस्याओं में भाग गया:

    app.config(function ($provide, $httpProvider) {
    $provide.factory('HttpInterceptor', function ($q, $location, AuthService) {
        return {
            'request': function (config) {
                console.log('in request interceptor.');
                if (!AuthService.isAuthenticated() && $location.path != '/login') {
                    console.log('user is not logged in.');
                    $location.path('/login');
                }
                return config;
            }
        };
    });

    $httpProvider.interceptors.push('HttpInterceptor');
});

एक और कारण है कि मैं चिंतित हूं कि एंगुलर डॉक्स में $ http पर अनुभाग निर्भरता प्राप्त करने के लिए एक तरीका दिखा रहा है ताकि "रेगुलर तरीका" को एक Http इंटरसेप्टर में इंजेक्ट किया जा सके। "इंटरसेप्टर्स" के तहत उनका कोड स्निपेट देखें:

// register the interceptor as a service
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
  return {
    // optional method
    'request': function(config) {
      // do something on success
      return config || $q.when(config);
    },

    // optional method
   'requestError': function(rejection) {
      // do something on error
      if (canRecover(rejection)) {
        return responseOrNewPromise
      }
      return $q.reject(rejection);
    },



    // optional method
    'response': function(response) {
      // do something on success
      return response || $q.when(response);
    },

    // optional method
   'responseError': function(rejection) {
      // do something on error
      if (canRecover(rejection)) {
        return responseOrNewPromise
      }
      return $q.reject(rejection);
    };
  }
});

$httpProvider.interceptors.push('myHttpInterceptor');

उपरोक्त कोड कहां जाना चाहिए?

मुझे लगता है कि मेरा सवाल यह है कि ऐसा करने का सही तरीका क्या है?

धन्यवाद, और मुझे आशा है कि मेरा प्रश्न पर्याप्त स्पष्ट था।


1
जिज्ञासा से बाहर, क्या निर्भरताएं - यदि कोई हो - जहां आप अपने प्रमाण सेवा में उपयोग कर रहे हैं? मुझे http इंटरसेप्टर में अनुरोध विधि का उपयोग करके परिपत्र निर्भरता के मुद्दे हो रहे थे, जो मुझे यहां लाया। मैं angularfire के $ firebaseAuth का उपयोग कर रहा हूं। जब मैंने इंजेक्टर (लाइन 510) से $ मार्ग का उपयोग करने वाले कोड के ब्लॉक को हटा दिया, तो सब कुछ काम करना शुरू कर दिया। यहाँ एक समस्या है, लेकिन यह इंटरसेप्टर में $ http का उपयोग करने के बारे में है। बंद करने के लिए!
स्लैमबॉर्न

एचएम, इसके लायक क्या है, मेरे मामले में AuthService $ खिड़की, $ http, $ स्थान, $ q
शुनुलीम

मुझे एक मामला मिला है जो कुछ परिस्थितियों में इंटरसेप्टर में अनुरोध को पुनः प्राप्त करता है, इसलिए इस पर और भी कम परिपत्र निर्भरता है $http। इसका एकमात्र तरीका जो मैंने पाया है वह उपयोग करना है $injector.get, लेकिन यह जानना बहुत अच्छा होगा कि इससे बचने के लिए कोड को संरचना करने का अच्छा तरीका है या नहीं।
मीकल चार्मेज़ा

1
@Rewritten: github.com/angular/angular.js/issues/2367 की प्रतिक्रिया पर एक नज़र डालें जो मेरे लिए एक समान समस्या तय करती है। वह जो कर रहा है वह इस प्रकार है: $ http = $ http || injector.get $ ( "$ http"); बेशक आप $ http को अपनी स्वयं की सेवा से बदल सकते हैं जिसे आप उपयोग करने की कोशिश कर रहे हैं।
जोनाथन

जवाबों:


42

आपके पास $ http और आपके AuthService के बीच एक परिपत्र निर्भरता है।

$injectorसेवा का उपयोग करके आप जो कुछ भी कर रहे हैं, वह चिकन एंड-एग समस्या को हल करने में देरी कर रहा है, ताकि ऑथेन्ट्स सर्विस पर $ http की निर्भरता में कमी आए।

मेरा मानना ​​है कि आपने जो किया वह वास्तव में करने का सबसे सरल तरीका है।

आप यह भी कर सकते हैं:

  • इंटरसेप्टर को बाद में पंजीकृत करना ( run()ब्लॉक के बजाय एक config()ब्लॉक में ऐसा करना पहले से ही चाल हो सकता है)। लेकिन क्या आप गारंटी दे सकते हैं कि $ http को पहले ही नहीं बुलाया गया है?
  • जब आप कॉल AuthService.setHttp()या कुछ करके इंटरसेप्टर को पंजीकृत कर रहे हों, तो आप HTTP में मैन्युअल रूप से $ http को "इंजेक्ट" कर रहे हैं ।
  • ...

15
यह उत्तर कैसे समस्या को हल कर रहा है, मैंने नहीं देखा? @shaunlim
Inanc Gumus

1
वास्तव में इसका हल न होना, इसका ठीक यही संकेत है कि एल्गोरिदम का प्रवाह बुरी तरह से था।
रोमन एम। कोस

12
आप किसी run()ब्लॉक में इंटरसेप्टर को पंजीकृत नहीं कर सकते , क्योंकि आप एक रन ब्लॉक में $ httpProvider को इंजेक्ट नहीं कर सकते। आप केवल कॉन्फ़िगरेशन चरण में ही ऐसा कर सकते हैं।
स्टीफन फ्रेडरिक

2
गुड पॉइंट री सर्कुलर रेफरेंस, लेकिन अन्यथा यह एक स्वीकृत उत्तर नहीं होना चाहिए। बुलेट के बिंदुओं में से कोई भी मतलब नहीं है
निकोलाई

65

यही मैंने समाप्त किया

  .config(['$httpProvider', function ($httpProvider) {
        //enable cors
        $httpProvider.defaults.useXDomain = true;

        $httpProvider.interceptors.push(['$location', '$injector', '$q', function ($location, $injector, $q) {
            return {
                'request': function (config) {

                    //injected manually to get around circular dependency problem.
                    var AuthService = $injector.get('Auth');

                    if (!AuthService.isAuthenticated()) {
                        $location.path('/login');
                    } else {
                        //add session_id as a bearer token in header of all outgoing HTTP requests.
                        var currentUser = AuthService.getCurrentUser();
                        if (currentUser !== null) {
                            var sessionId = AuthService.getCurrentUser().sessionId;
                            if (sessionId) {
                                config.headers.Authorization = 'Bearer ' + sessionId;
                            }
                        }
                    }

                    //add headers
                    return config;
                },
                'responseError': function (rejection) {
                    if (rejection.status === 401) {

                        //injected manually to get around circular dependency problem.
                        var AuthService = $injector.get('Auth');

                        //if server returns 401 despite user being authenticated on app side, it means session timed out on server
                        if (AuthService.isAuthenticated()) {
                            AuthService.appLogOut();
                        }
                        $location.path('/login');
                        return $q.reject(rejection);
                    }
                }
            };
        }]);
    }]);

नोट: $injector.getकॉल इंटरसेप्टर के तरीकों के भीतर होनी चाहिए, यदि आप उन्हें अन्यत्र उपयोग करने का प्रयास करते हैं तो आपको JS में एक परिपत्र निर्भरता त्रुटि मिलती रहेगी।


4
मैनुअल इंजेक्शन ($ injector.get ('Auth')) का उपयोग करके हल की गई समस्या। अच्छा कार्य!
रॉबर्ट

सर्कुलर निर्भरता से बचने के लिए मैं जाँच करता हूँ कि url किसे कहा जाता है। if (config.url.includes ('/ oauth / v2 / token') && config.url.includes ('/ api')) {// Call OAuth Service}। इसके लिए और अधिक परिपत्र निर्भरता नहीं है। कम से कम खुद के लिए यह काम किया;)।
ब्रीच

उत्तम। ठीक इसी तरह की समस्या को ठीक करने के लिए मुझे इसकी आवश्यकता थी। धन्यवाद @ अंशुमल!
मार्टिन चैम्बरलिन

मुझे यह समाधान वास्तव में पसंद नहीं है क्योंकि तब यह सेवा गुमनाम है और परीक्षणों के लिए संभालना आसान नहीं है। रनटाइम पर इंजेक्ट करने के लिए बहुत बेहतर उपाय।
kmanzana

मेरे लिए यही काम किया। मूल रूप से $ http का उपयोग करने वाली सेवा को इंजेक्ट किया जा रहा है।
थॉमस

15

मुझे लगता है कि सीधे $ इंजेक्टर का उपयोग करना एक एंटीपैटर्न है।

परिपत्र निर्भरता को तोड़ने का एक तरीका एक घटना का उपयोग करना है: $ राज्य को इंजेक्ट करने के बजाय, $ rootScope को इंजेक्ट करें। सीधे पुनर्निर्देशित करने के बजाय, करें

this.$rootScope.$emit("unauthorized");

प्लस

angular
    .module('foo')
    .run(function($rootScope, $state) {
        $rootScope.$on('unauthorized', () => {
            $state.transitionTo('login');
        });
    });

2
मुझे लगता है कि यह अधिक सुरुचिपूर्ण समाधान है, क्योंकि इसमें कोई निर्भरता नहीं होगी, हम इस घटना को कई स्थानों पर सुन सकते हैं जहां प्रासंगिक है
बसव

यह मेरी जरूरतों के लिए काम नहीं करेगा, क्योंकि किसी घटना को भेजने के बाद मुझे रिटर्न वैल्यू नहीं मिल सकती है।
xabitrigo

13

बुरे तर्क ने ऐसे परिणाम दिए

वास्तव में Http Interceptor में उपयोगकर्ता अधिकृत है या नहीं, इसका कोई मतलब नहीं है। मैं आपके सभी HTTP अनुरोधों को एकल .service (या .factory, या .provider) में लपेटने के लिए पुनः उपयोग करूंगा, और सभी अनुरोधों के लिए इसका उपयोग करूंगा। हर बार जब आप फ़ंक्शन को कॉल करते हैं, तो आप जांच सकते हैं कि उपयोगकर्ता लॉग इन है या नहीं। यदि सब ठीक है, तो अनुरोध भेजें।

आपके मामले में, कोणीय आवेदन किसी भी मामले में अनुरोध भेजेगा, आप बस वहां प्राधिकरण की जांच करेंगे, और उसके बाद जावास्क्रिप्ट अनुरोध भेजेंगे।

आपकी समस्या का मूल

myHttpInterceptor$httpProviderउदाहरण के लिए कहा जाता है । आपके AuthServiceउपयोग $http, या $resource, और यहां आपके पास निर्भरता पुनरावर्तन, या परिपत्र निर्भरता है। यदि आपकी उस निर्भरता को AuthServiceआप से दूर करते हैं , तो आप उस त्रुटि को नहीं देखेंगे।


जैसा कि @ पीटर हिरोलेन ने बताया, आप इस इंटरसेप्टर को अपने मॉड्यूल में रख सकते हैं module.run, लेकिन यह हैक की तरह होगा, समाधान नहीं।

यदि आपका स्वच्छ और स्व वर्णनात्मक कोड अप करने के लिए है, तो आपको कुछ ठोस सिद्धांतों के साथ जाना चाहिए।

कम से कम सिंगल रिस्पॉन्सिबिलिटी सिद्धांत आपको ऐसी स्थितियों में बहुत मदद करेगा।


5
मुझे नहीं लगता कि यह उत्तर अच्छी तरह से शब्द है, लेकिन मुझे लगता है कि यह इस मुद्दे की जड़ में जाता है। किसी अन्य सेवा के साथ समस्या, जो वर्तमान उपयोगकर्ता डेटा और लॉग इन (HTTP अनुरोध) को संग्रहीत करती है, यह दो चीजों के लिए जिम्मेदार है । यदि इसके बजाय वर्तमान उपयोगकर्ता डेटा को संग्रहीत करने के लिए एक सेवा में विभाजित किया गया है, और लॉग इन करने के लिए दूसरी सेवा है, तो http इंटरसेप्टर को केवल "वर्तमान उपयोगकर्ता सेवा" पर निर्भर होने की आवश्यकता है, और अब एक परिपत्र निर्भरता नहीं बनाता है।
Snixtor

@Snixtor धन्यवाद! मुझे और अधिक स्पष्ट होने के लिए, अंग्रेजी सीखना होगा।
रोमन एम। कोस

0

यदि आप केवल प्रामाणिक स्थिति (isAuthorized ()) के लिए जाँच कर रहे हैं, तो मैं उस राज्य को एक अलग मॉड्यूल में रखने की सलाह दूंगा, "प्रामाणिक", जो केवल राज्य को धारण करता है और $ http का उपयोग नहीं करता है।

app.config(['$httpProvider', function ($httpProvider) {
  $httpProvider.interceptors.push(function ($location, Auth) {
    return {
      'request': function (config) {
        if (!Auth.isAuthenticated() && $location.path != '/login') {
          console.log('user is not logged in.');
          $location.path('/login');
        }
        return config;
      }
    }
  })
}])

प्रामाणिक मॉड्यूल:

angular
  .module('app')
  .factory('Auth', Auth)

function Auth() {
  var $scope = {}
  $scope.sessionId = localStorage.getItem('sessionId')
  $scope.authorized = $scope.sessionId !== null
  //... other auth relevant data

  $scope.isAuthorized = function() {
    return $scope.authorized
  }

  return $scope
}

(मैंने क्लाइंटस्ट पर सेशनआईड को स्टोर करने के लिए लोकलस्टोरेज का उपयोग किया था, लेकिन आप उदाहरण के लिए $ http कॉल के बाद इसे अपने AuthService के अंदर भी सेट कर सकते हैं)

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.