AngularJS: Hizmeti bir HTTP engelleyicisine enjekte etme (Dairesel bağımlılık)


118

AngularJS uygulamamın kimlik doğrulamasını işleyebilmesi için bir HTTP yakalayıcı yazmaya çalışıyorum.

Bu kod işe yarıyor, ancak Angular'ın bunu otomatik olarak halletmesi gerektiğini düşündüğüm için bir hizmeti manuel olarak enjekte etmekten endişe duyuyorum:

    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;
            }
        };
    })
}]);

Yapmaya başladığım şey, ancak döngüsel bağımlılık sorunları ile karşılaştım:

    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');
});

Endişelenmemin bir başka nedeni de, Angular Docs içindeki $ http ile ilgili bölümün, bağımlılıkları bir Http durdurucuya "normal yoldan" enjekte etmenin bir yolunu gösteriyor gibi görünmesidir. Kod snippet'lerini "Interceptors" altında görün:

// 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');

Yukarıdaki kod nereye gitmeli?

Sanırım sorum şu, bunu yapmanın doğru yolu nedir?

Teşekkürler ve umarım sorum yeterince açıktı.


1
Sadece meraktan soruyorum, AuthService'inizde hangi bağımlılıkları - varsa - nerede kullanıyorsunuz? Beni buraya getiren http engelleyicideki istek yöntemini kullanarak döngüsel bağımlılık sorunları yaşıyordum. Angularfire's $ firebaseAuth kullanıyorum. $ Route kullanan kod bloğunu enjektörden kaldırdığımda (satır 510), her şey çalışmaya başladı. Burada bir sorun var ama bu, önleyicide $ http kullanmakla ilgili. Git git!
slamborne

Hm, değeri ne olursa olsun, benim durumumdaki AuthService $ window, $ http, $ location, $ q
shaunlim

Bazı durumlarda durdurucudaki talebi yeniden deneyen bir vakam var, bu yüzden daha da kısa bir döngüsel bağımlılık var $http. Bulduğum tek yol kullanmaktır $injector.get, ancak bundan kaçınmak için kodu yapılandırmanın iyi bir yolu olup olmadığını bilmek harika olurdu.
Michal Charemza

1
Benim için benzer bir sorunu çözen @rewritten: github.com/angular/angular.js/issues/2367'den gelen yanıta bir göz atın . Yaptığı şey şu: $ http = $ http || injector.get, $ ( "$ http"); tabii ki $ http'yi kullanmaya çalıştığınız kendi hizmetinizle değiştirebilirsiniz.
Jonathan

Yanıtlar:


42

$ Http ile AuthService arasında döngüsel bir bağımlılığınız var.

$injectorHizmeti kullanarak yaptığınız şey, $ http'nin AuthService'e bağımlılığını geciktirerek tavuk ve yumurta sorununu çözmektir.

Yaptığın şeyin aslında bunu yapmanın en basit yolu olduğuna inanıyorum.

Bunu şu şekilde de yapabilirsiniz:

  • Durdurucuyu daha sonra kaydetmek (bunu bir run()blok yerine bir config()blokta yapmak zaten hile yapabilir). Ama $ http'nin daha önce çağrılmadığını garanti edebilir misiniz?
  • Durdurucuyu arayarak AuthService.setHttp()veya başka bir şeyle kaydederken $ http'yi AuthService'e manuel olarak "enjekte etme" .
  • ...

15
Bu cevap sorunu nasıl çözüyor, görmedim mi? @shaunlim
İnanç Gümüş

1
Aslında çözmüyor, sadece algoritma akışının kötü olduğuna işaret ediyor.
Roman M. Koss

12
Bir de önleme kayıt olamaz run()olamaz çünkü iğne $ httpProvider bir çalışma bloğa, bloğun. Bunu yalnızca yapılandırma aşamasında yapabilirsiniz.
Stephen Friedrich

2
İyi bir nokta döngüsel referanstır, ancak aksi takdirde kabul edilen bir cevap olmamalıdır. Madde işaretleri hiçbir anlam ifade etmiyor
Nikolai

65

Sonunda yaptığım şey bu

  .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);
                    }
                }
            };
        }]);
    }]);

Not: $injector.getÇağrılar, durdurucunun yöntemleri içinde olmalıdır, eğer onları başka bir yerde kullanmaya çalışırsanız, JS'de döngüsel bir bağımlılık hatası almaya devam edeceksiniz.


4
Manuel enjeksiyon kullanmak ($ ​​injector.get ('Auth')) sorunu çözdü. İyi iş!
Robert

Döngüsel bağımlılığı önlemek için hangi url'nin çağrıldığını kontrol ediyorum. if (! config.url.includes ('/ oauth / v2 / token') && config.url.includes ('/ api')) {// OAuth Hizmetini Arayın}. Bundan dolayı artık döngüsel bağımlılık yoktur. En azından kendim için işe yaradı;).
Brieuc

Mükemmel. Benzer bir sorunu çözmek için ihtiyacım olan şey tam olarak buydu. Teşekkürler @shaunlim!
Martyn Chamberlin

Bu çözümü gerçekten sevmiyorum çünkü o zaman bu hizmet anonim ve testler için üstesinden gelmek o kadar kolay değil. Çalışma zamanında enjekte etmek için çok daha iyi bir çözüm.
kmanzana

Bu benim için çalıştı. Temel olarak $ http kullanan hizmeti enjekte etmek.
Thomas

15

Doğrudan $ enjektörü kullanmanın bir anti-model olduğunu düşünüyorum.

Döngüsel bağımlılığı kırmanın bir yolu bir olay kullanmaktır: $ state enjekte etmek yerine $ rootScope enjekte edin. Doğrudan yeniden yönlendirmek yerine

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

artı

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

2
Bunun daha zarif bir çözüm olduğunu düşünüyorum, çünkü herhangi bir bağımlılığı olmayacak, bu etkinliği ilgili her yerde birçok yerde dinleyebiliriz
Basav

Bir olay gönderdikten sonra bir dönüş değerine sahip olamayacağım için bu ihtiyaçlarım için çalışmaz.
xabitrigo

13

Kötü mantık böyle sonuçlar verdi

Aslında, Http Interceptor'da kullanıcı tarafından yazılan veya yazılmayan bir arama noktası yoktur. Tüm HTTP isteklerinizi tek bir .service (veya .factory veya .provider içine) içine almanızı ve TÜM istekler için kullanmanızı tavsiye ederim. Fonksiyonu her çağırdığınızda, kullanıcının oturum açıp açmadığını kontrol edebilirsiniz. Her şey yolundaysa, istek göndermeye izin verin.

Sizin durumunuzda, Angular uygulaması her durumda istek gönderir, siz sadece orada yetkilendirmeyi kontrol edersiniz ve bundan sonra JavaScript istek gönderir.

Probleminin özü

myHttpInterceptor$httpProviderörnek altında denir . Kişisel AuthServicekullanımlar $httpveya $resourceve burada bağımlılık Özyinelemeyi veya döngüsel bağımlılığı var. Bu bağımlılığı 'den kaldırırsanız, AuthServiceo hatayı görmezsiniz.


Ayrıca @Pieter Herroelen'in işaret ettiği gibi, bu engelleyiciyi modülünüze yerleştirebilirsiniz module.run, ancak bu bir çözüm değil, daha çok bir hack gibi olacaktır.

Temiz ve kendi kendini tanımlayan bir kod yapacaksanız, bazı SOLID ilkelerine uymanız gerekir.

En azından Tek Sorumluluk ilkesi bu tür durumlarda size çok yardımcı olacaktır.


5
Bu cevap iyi ifadeli sanmıyorum ama bunu o konunun köküne alır düşünüyorum. Mevcut kullanıcı verilerini ve oturum açma yöntemini (bir http isteği) depolayan bir kimlik doğrulama hizmetiyle ilgili sorun, iki şeyden sorumlu olmasıdır . Bunun yerine, mevcut kullanıcı verilerini depolamak için bir hizmete ve oturum açmak için başka bir hizmete bölünmüşse, http durdurucunun yalnızca "mevcut kullanıcı hizmetine" bağlı olması gerekir ve artık döngüsel bir bağımlılık oluşturmaz.
Snixtor

@Snixtor Teşekkürler! Daha net olmak için daha fazla İngilizce öğrenmem gerekiyor.
Roman M. Koss

0

Yetkilendirme durumunu (isAuthorized ()) kontrol ediyorsanız, bu durumu ayrı bir modüle koymanızı tavsiye ederim, sadece durumu tutan ve $ http'nin kendisini kullanmayan "Yetkilendirme" deyin.

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;
      }
    }
  })
}])

Kimlik Doğrulama Modülü:

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
}

(burada sessionId'yi istemci tarafında saklamak için localStorage kullandım, ancak bunu örneğin bir $ http çağrısından sonra AuthService'inizde de ayarlayabilirsiniz)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.