javascript - parameter - jqxhr




如何在jQuery Ajax調用後管理重定向請求 (20)

我使用$.post()來使用Ajax調用servlet,然後使用生成的HTML片段替換用戶當前頁面中的div元素。 但是,如果會話超時,服務器將發送重定向指令以將用戶發送到登錄頁面。 在這種情況下,jQuery正在用登錄頁面的內容替換div元素,強制用戶的眼睛確實見證一個罕見的場景。

我如何使用jQuery 1.2.6從Ajax調用中管理重定向指令?


Additionally you will probably want to redirect user to the given in headers URL. So finally it will looks like this:

$.ajax({
    //.... other definition
    complete:function(xmlHttp){
        if(xmlHttp.status.toString()[0]=='3'){
        top.location.href = xmlHttp.getResponseHeader('Location');
    }
});

UPD: Opps. Have the same task, but it not works. Doing this stuff. I'll show you solution when I'll find it.


I got a working solulion using the answers from @John and @Arpad .com/a/8426947/4505142 and @RobWinch link

I use Spring Security 3.2.9 and jQuery 1.10.2.

Extend Spring's class to cause 4XX response only from AJAX requests:

public class CustomLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {

    public CustomLoginUrlAuthenticationEntryPoint(final String loginFormUrl) {
        super(loginFormUrl);
    }

    // For AJAX requests for user that isn't logged in, need to return 403 status.
    // For normal requests, Spring does a (302) redirect to login.jsp which the browser handles normally.
    @Override
    public void commence(final HttpServletRequest request,
                         final HttpServletResponse response,
                         final AuthenticationException authException)
            throws IOException, ServletException {
        if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
        } else {
            super.commence(request, response, authException);
        }
    }
}

applicationContext-security.xml

  <security:http auto-config="false" use-expressions="true" entry-point-ref="customAuthEntryPoint" >
    <security:form-login login-page='/login.jsp' default-target-url='/index.jsp'                             
                         authentication-failure-url="/login.jsp?error=true"
                         />    
    <security:access-denied-handler error-page="/errorPage.jsp"/> 
    <security:logout logout-success-url="/login.jsp?logout" />
...
    <bean id="customAuthEntryPoint" class="com.myapp.utils.CustomLoginUrlAuthenticationEntryPoint" scope="singleton">
        <constructor-arg value="/login.jsp" />
    </bean>
...
<bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache">
    <property name="requestMatcher">
      <bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
        <constructor-arg>
          <bean class="org.springframework.security.web.util.matcher.MediaTypeRequestMatcher">
            <constructor-arg>
              <bean class="org.springframework.web.accept.HeaderContentNegotiationStrategy"/>
            </constructor-arg>
            <constructor-arg value="#{T(org.springframework.http.MediaType).APPLICATION_JSON}"/>
            <property name="useEquals" value="true"/>
          </bean>
        </constructor-arg>
      </bean>
    </property>
</bean>

In my JSPs, add a global AJAX error handler as shown here

  $( document ).ajaxError(function( event, jqxhr, settings, thrownError ) {
      if ( jqxhr.status === 403 ) {
          window.location = "login.jsp";
      } else {
          if(thrownError != null) {
              alert(thrownError);
          } else {
              alert("error");
          }
      }
  });

Also, remove existing error handlers from AJAX calls in JSP pages:

        var str = $("#viewForm").serialize();
        $.ajax({
            url: "get_mongoDB_doc_versions.do",
            type: "post",
            data: str,
            cache: false,
            async: false,
            dataType: "json",
            success: function(data) { ... },
//            error: function (jqXHR, textStatus, errorStr) {
//                 if(textStatus != null)
//                     alert(textStatus);
//                 else if(errorStr != null)
//                     alert(errorStr);
//                 else
//                     alert("error");
//            }
        });

I hope it helps others.

Update1 I found that I needed to add the option (always-use-default-target="true") to the form-login config. This was needed since after an AJAX request gets redirected to the login page (due to expired session), Spring remembers the previous AJAX request and auto redirects to it after login. This causes the returned JSON to be displayed on the browser page. Of course, not what I want.

Update2 Instead of using always-use-default-target="true" , use @RobWinch example of blocking AJAX requests from the requstCache. This allows normal links to be redirected to their original target after login, but AJAX go to the home page after login.


You can also hook XMLHttpRequest send prototype. This will work for all sends (jQuery/dojo/etc) with one handler.

I wrote this code to handle a 500 page expired error, but it should work just as well to trap a 200 redirect. Ready the wikipedia entry on XMLHttpRequest onreadystatechange about the meaning of readyState.

// Hook XMLHttpRequest
var oldXMLHttpRequestSend = XMLHttpRequest.prototype.send;

XMLHttpRequest.prototype.send = function() {
  //console.dir( this );

  this.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 500 && this.responseText.indexOf("Expired") != -1) {
      try {
        document.documentElement.innerHTML = this.responseText;
      } catch(error) {
        // IE makes document.documentElement read only
        document.body.innerHTML = this.responseText;
      }
    }
  };

  oldXMLHttpRequestSend.apply(this, arguments);
}

使用ASP.NET MVC RedirectToAction方法可能會出現此問題。 為了防止表單在div中顯示響應,您可以簡單地使用某種類型的ajax響應篩選器來輸入$ .ajaxSetup的響應。 如果響應包含MVC重定向,則可以在JS端評估此表達式。 下面的JS示例代碼:

$.ajaxSetup({
    dataFilter: function (data, type) {
        if (data && typeof data == "string") {
            if (data.indexOf('window.location') > -1) {
                eval(data);
            }
        }
        return data;
    }
});

如果數據是: “window.location ='/ Acount / Login'”,則上面的過濾器將捕獲該數據並評估以進行重定向,而不是讓數據顯示。


嘗試

    $(document).ready(function () {
        if ($("#site").length > 0) {
            window.location = "<%= Url.Content("~") %>" + "Login/LogOn";
        }
    });

把它放在登錄頁面上。 如果它被加載到主頁面上的div上,它將重定向到登錄頁面。 “#site”是位於除登錄頁面以外的所有頁面上的div的id。


在servlet中你應該放置response.setStatus(response.SC_MOVED_PERMANENTLY); 發送重定向所需的'301'xmlHttp狀態...

並且在$ .ajax函數中,你不應該使用.toString()函數......

if (xmlHttp.status == 301) { top.location.href = 'xxxx.jsp'; }

問題是它不是很靈活,你不能決定你想要重定向的位置。

通過servlet重定向應該是最​​好的方法。 但我仍然無法找到正確的做法。


如果你還想傳遞值,那麼你也可以設置會話變量和訪問例如:在你的jsp中,你可以寫

<% HttpSession ses = request.getSession(true);
   String temp=request.getAttribute("what_you_defined"); %>

然後,您可以將此臨時值存儲在您的JavaScript變量中並進行播放


將弗拉基米爾普魯德尼科夫和托馬斯漢森的觀點放在一起:

  • 更改您的服務器端代碼以檢測它是否是XHR。 如果是,請將重定向的響應代碼設置為278.在django中:
   if request.is_ajax():
      response.status_code = 278

這使瀏覽器將響應視為成功,並將其交給您的Javascript。

  • 在你的JS中,確保表單提交是通過Ajax,檢查響應代碼並在需要時重定向:
$('#my-form').submit(function(event){ 

  event.preventDefault();   
  var options = {
    url: $(this).attr('action'),
    type: 'POST',
    complete: function(response, textStatus) {    
      if (response.status == 278) { 
        window.location = response.getResponseHeader('Location')
      }
      else { ... your code here ... } 
    },
    data: $(this).serialize(),   
  };   
  $.ajax(options); 
});

我只是想分享我的方法,因為這可能會幫助某人:

我基本上包括一個JavaScript模塊,用於處理認證內容,如顯示用戶名,以及處理重定向到登錄頁面的情況

我的場景:我們之間基本上有一個ISA服務器,它監聽所有請求, 在我們的登錄頁面使用302和位置標題進行響應

在我的JavaScript模塊中,我的初始方法是類似的

$(document).ajaxComplete(function(e, xhr, settings){
    if(xhr.status === 302){
        //check for location header and redirect...
    }
});

這個問題(這裡已經提到的很多)是,瀏覽器自己處理重定向,因此我的ajaxComplete回調沒有被調用,但是我得到了已經重定向的Login頁面響應,這顯然是status 200 。 問題:你如何檢測成功的200響應是你的實際登錄頁面還是其他任意頁面?

解決方案

由於我無法捕獲302重定向響應,因此我在登錄頁面上添加了一個LoginPage標題,其中包含登錄頁面本身的url。 在模塊中,我現在聽標題並做一個重定向:

if(xhr.status === 200){
    var loginPageRedirectHeader = xhr.getResponseHeader("LoginPage");
    if(loginPageRedirectHeader && loginPageRedirectHeader !== ""){
        window.location.replace(loginPageRedirectHeader);
    }
}

...而且它的作用像魅力:)。 您可能想知道為什麼我將url包含在LoginPage標題中......基本上,因為我沒有發現確定從xhr對象的自動位置重定嚮導致的GET url的方法...


我喜歡Timmerz的方法,加上少許檸檬。 如果您在期待JSON時獲得了text / html的返回contentType ,則很可能被重定向。 就我而言,我只是簡單地重新加載頁面,並將其重定向到登錄頁面。 哦,並檢查jqXHR狀態是200,這看起來很愚蠢,因為你在錯誤功能,對不對? 否則,合法的錯誤情況將強制迭代重新加載(oops)

$.ajax(
   error:  function (jqXHR, timeout, message) {
    var contentType = jqXHR.getResponseHeader("Content-Type");
    if (jqXHR.status === 200 && contentType.toLowerCase().indexOf("text/html") >= 0) {
        // assume that our login has expired - reload our current page
        window.location.reload();
    }

});

我沒有任何成功的頭解決方案 - 他們從來沒有拿起我的ajaxSuccess / ajaxComplete方法。 我用Steg的回答與自定義的回應,但我修改了JS方面的一些。 我設置了一個我在每個函數中調用的方法,這樣我就可以使用標準的$.get$.post方法。

function handleAjaxResponse(data, callback) {
    //Try to convert and parse object
    try {
        if (jQuery.type(data) === "string") {
            data = jQuery.parseJSON(data);
        }
        if (data.error) {
            if (data.error == 'login') {
                window.location.reload();
                return;
            }
            else if (data.error.length > 0) {
                alert(data.error);
                return;
            }
        }
    }
    catch(ex) { }

    if (callback) {
        callback(data);
    }
}

它正在使用的示例...

function submitAjaxForm(form, url, action) {
    //Lock form
    form.find('.ajax-submit').hide();
    form.find('.loader').show();

    $.post(url, form.serialize(), function (d) {
        //Unlock form
        form.find('.ajax-submit').show();
        form.find('.loader').hide();

        handleAjaxResponse(d, function (data) {
            // ... more code for if auth passes ...
        });
    });
    return false;
}

我發現的另一個解決方案(如果您想設置全局行為,特別有用)是使用$.ajaxsetup()方法statusCode屬性 。 像其他人一樣指出,不要使用重定向狀態碼( 3xx ),而應使用4xx並處理重定向客戶端。

$.ajaxSetup({ 
  statusCode : {
    400 : function () {
      window.location = "/";
    }
  }
});

400替換為您要處理的狀態碼。 就像已經提到的401 Unauthorized可能是一個好主意。 我使用400因為它非常不明確,我可以將401用於更具體的情況(如錯誤的登錄憑證)。 因此,當會話超時並且您處理重定向客戶端時,不要直接重定向您的後端應返回4xx錯誤代碼。 即使有像backbone.js這樣的框架,也適合我


我解決了這個問題:

  1. 將自定義標題添加到響應中:

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
    
  2. 將JavaScript函數綁定到ajaxSuccess事件並檢查頭是否存在:

    $(document).ajaxSuccess(function(event, request, settings) {
        if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
           window.location = '/';
        }
    });
    

我解決了這個問題:

添加一個中間件來處理響應,如果它是ajax請求的重定向,請將響應更改為具有重定向url的正常響應。

class AjaxRedirect(object):
  def process_response(self, request, response):
    if request.is_ajax():
      if type(response) == HttpResponseRedirect:
        r = HttpResponse(json.dumps({'redirect': response['Location']}))
        return r
    return response

然後在ajaxComplete中,如果響應包含重定向,它必須是重定向,所以更改瀏覽器的位置。

  $('body').ajaxComplete(function (e, xhr, settings) {
    if (xhr.status == 200) {
      var redirect = null;
      try {
        redirect = $.parseJSON(xhr.responseText).redirect;
        if (redirect) {
          window.location.href = redirect.replace(/\?.*$/, "?next=" + window.location.pathname);
        }
      } catch (e) {
        return;
      }

    }

我讀過這個問題,並實現了將響應狀態代碼設置為278的方法,以避免瀏覽器透明地處理重定向。 儘管這有效,但我有點不滿意,因為它有點破解。

經過更多的挖掘,我放棄了這種方法並使用JSON 。 在這種情況下,對ajax請求的所有響應都具有狀態碼200,並且響應的主體包含在服務器上構建的JSON對象。 客戶端上的JavaScript可以使用JSON對象來決定它需要做什麼。

我遇到了類似的問題。 我執行一個ajax請求,它有兩種可能的響應:一種是將瀏覽器重定向到一個新頁面,另一種是用當前頁面替換當前頁面上的現有HTML表單。 這樣做的jQuery代碼看起來像這樣:

$.ajax({
    type: "POST",
    url: reqUrl,
    data: reqBody,
    dataType: "json",
    success: function(data, textStatus) {
        if (data.redirect) {
            // data.redirect contains the string URL to redirect to
            window.location.href = data.redirect;
        }
        else {
            // data.form contains the HTML for the replacement form
            $("#myform").replaceWith(data.form);
        }
    }
});

JSON對象“data”在服務器上構建為具有2個成員:data.redirect和data.form。 我發現這種方法要好得多。


我通過在login.php頁面中添加以下內容來解決此問題。

<script type="text/javascript">
    if (top.location.href.indexOf('login.php') == -1) {
        top.location.href = '/login.php';
    }
</script>

最終實現的解決方案是為Ajax調用的回調函數使用包裝,並在此包裝中檢查返回的HTML塊中是否存在特定元素。 如果找到元素,則包裝器執行重定向。 如果不是,包裝將呼叫轉發到實際的回調函數。

例如,我們的包裝函數是這樣的:

function cbWrapper(data, funct){
    if($("#myForm", data).length > 0)
        top.location.href="login.htm";//redirection
    else
        funct(data);
}

然後,在進行Ajax調用時,我們使用了以下內容:

$.post("myAjaxHandler", 
       {
        param1: foo,
        param2: bar
       },
       function(data){
           cbWrapper(data, myActualCB);
       }, 
       "html"
);

這對我們很有用,因為所有Ajax調用總是在我們用來替換頁面的一部分的DIV元素內返回HTML。 另外,我們只需要重定向到登錄頁面。


有些人可能會發現以下有用的:

我希望客戶端被重定向到登錄頁面,以便發送沒有授權令牌的任何休息操作。 由於我的所有休息操作都是基於Ajax的,因此我需要一種很好的通用方式來重定向到登錄頁面,而不是處理Ajax成功函數。

這是我所做的:

在任何Ajax請求中,我的服務器將返回一個“需要認證”的Json 200響應(如果客戶端需要認證)。

Java中的簡單示例(服務器端):

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    private final Logger m_logger = LoggerFactory.getLogger(AuthenticationFilter.class);

    public static final String COOKIE_NAME = "token_cookie"; 

    @Override
    public void filter(ContainerRequestContext context) throws IOException {        
        // Check if it has a cookie.
        try {
            Map<String, Cookie> cookies = context.getCookies();

            if (!cookies.containsKey(COOKIE_NAME)) {
                m_logger.debug("No cookie set - redirect to login page");
                throw new AuthenticationException();
            }
        }
        catch (AuthenticationException e) {
            context.abortWith(Response.ok("\"NEED TO AUTHENTICATE\"").type("json/application").build());
        }
    }
}

在我的Javascript中,我添加了下面的代碼:

$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    var originalSuccess = options.success;

    options.success = function(data) {
        if (data == "NEED TO AUTHENTICATE") {
            window.location.replace("/login.html");
        }
        else {
            originalSuccess(data);
        }
    };      
});

這就是它。


這對我有用:

success: function(data, textStatus, xhr) {

        console.log(xhr.status);
}

一旦成功,ajax將獲得瀏覽器從服務器獲取並執行的相同狀態代碼。


雖然答案似乎適用於人們,如果你使用Spring Security,我發現擴展了LoginUrlAuthenticationEntryPoint並添加了特定的代碼來處理AJAX更強大。 大多數示例攔截所有重定向,而不僅僅是身份驗證失敗。 這對我所從事的項目來說是不理想的。 如果您不希望失敗的AJAX請求被緩存,您可能會發現還需要擴展ExceptionTranslationFilter並覆蓋“sendStartAuthentication”方法以刪除緩存步驟。

示例AjaxAwareAuthenticationEntryPoint:

public class AjaxAwareAuthenticationEntryPoint extends
    LoginUrlAuthenticationEntryPoint {

public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
    super(loginUrl);
}

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    if (isAjax(request)) {
        response.sendError(HttpStatus.UNAUTHORIZED.value(), "Please re-authenticate yourself");
    } else {
        super.commence(request, response, authException);
    }
}

public static boolean isAjax(HttpServletRequest request) {
    return request != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}

}

來源: 2





redirect