springboot 重定向url变乱

springboot请求访问springboot接口,参数为另外一个url,url中含有# ,
springboot中获得参数时无法获得#和#后面部分;
例如
http://ip/SvgHome.html#/compiles


        TokenData body = restTemplate.getForEntity(URL, TokenData.class).getBody();
        assert body != null;
        log.info(body.getResult());

        String param=url+"&token="+body.getResult();
        log.info(param);
        modelAndView.setViewName("redirect:"+param);
        return modelAndView;

实际跳转路径为http://ip:9143/SvgHome.html&token=aece153ba6b16a1b#/compiles

即把token部分拼接在中间了

查了写资料,说是#的缘故,但是不知道怎么处理,
上面那个问题不会的,解决下面这个也行,

springboot获得参数中包含#的部分,网上说配置tomcatconfig,但是我这个不管用

最近在项目中遇到了一个问题,需求变更要求,url中需要支持特殊字符,例如:“.”,“@”等。由于项目中包含了很多已经开放的接口,采用的是restful风格的,因此改接口方案直接被pass。在查找了很多资料,最终解决这个问题。

问题主要包括三个:

包含特殊url请求时,@ResponseBody 注解的请求,返回时会抛出异常
如何匹配包含特殊字符的url,例如/email/huang@gmail.com
采用注解方式自定义异常以json格式返回的,在包含特殊Url时,无法抛出自己想要的异常格式
注意:本文spring boot版本是基于1.5.16 (2.0以后可能有所不同)

问题一:包含特殊url请求时,@ResponseBody 注解的请求,返回时会抛出异常
package com.example.demo.controller;

import com.example.demo.domain.RestfulAPIResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**

public class RestfulAPIResponse implements Serializable {

private String requestId;

private Map<String,R> result;

public RestfulAPIResponse(String requestId){
    result = new HashMap<String,R>();
    this.requestId = requestId;
}

public String getRequestId() {
    return requestId;
}

public void setRequestId(String requestId) {
    this.requestId = requestId;
}


public Map getResult() {
    return result;
}

public void setResult(String key,R value) {
    this.result.put(key,value);
}

public void setResultMap(Map<String, R> map) {
    if (map != null && map.size() > 0) {
        result.putAll(map);
    }
}

}

请求 url : http://127.0.0.1:8080/user/huangyichun_@.com 返回值如下:

{
"timestamp": 1546072805982,
"status": 406,
"error": "Not Acceptable",
"exception": "org.springframework.web.HttpMediaTypeNotAcceptableException",
"message": "Could not find acceptable representation",
"path": "/user/huangyichun_@.com"
}
这个问题是由于使用外部Tomcat导致的,如果直接调用Spring boot的main方法启动的内部tomcat不会产生这个问题。

解决方法:添加如下配置,问题解决

package com.example.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**

  • @author huangyichun

  • @date 2018/12/29

  • /
    @Configuration
    public class SpringServiceConfiguration extends WebMvcConfigurationSupport {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {

     // to  avoid HttpMediaTypeNotAcceptableException on standalone tomcat
      configurer.favorPathExtension(false);
    

    }
    }

调用url: 请求 url : http://127.0.0.1:8080/user/huangyichun_@.com 返回值如下:

{
"requestId": "requestId",
"result": {
"name": "huangyichun_@"
}
}
可以看出返回格式是正常了,但是发现返回的name和我们请求的name不太一样,这是因为Spring将url中的"."看做了分隔符,因此产生了问题二。

问题二:如何匹配包含特殊字符的url,例如/email/huang@gmail.com
解决方法如下:

package com.example.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**

  • @author huangyichun

  • @date 2018/12/29

  • /
    @Configuration
    public class SpringServiceConfiguration extends WebMvcConfigurationSupport {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {

      // to  avoid HttpMediaTypeNotAcceptableException on standalone tomcat
      configurer.favorPathExtension(false); 
    

    }

    /**

    • 解决url后缀包含特殊字符
    • @param configurer
    • /
      @Override
      public void configurePathMatch(PathMatchConfigurer configurer) {
      configurer.setUseSuffixPatternMatch(false);
      }
      }
      请求上面的url,返回结果如下:

{
"requestId": "requestId",
"result": {
"name": "huangyichun_@.com"
}
}
问题三:采用注解方式自定义异常以json格式返回的,在包含特殊Url时,无法抛出自己想要的异常格式
    该场景出现的比较特殊,是在Controller方法中,使用RestTemplate调用其他服务,例如查询用户是否存在,结果为不存在,然后该服务抛出了一个not fount 404的json异常信息。项目中采用了注解方法进行异常处理,在返回时,Spring进行序列化后导致结果不是自己想要的格式。

解决思路:

让自定义异常处理类继承HandlerExceptionResolver,然后实现默认方法,对于抛出的异常进行最后的转换,结果符合自己需求。下面直接给出参考代码,和上面示例不是同一个项目,仅供参考

@ControllerAdvice
public class CustomExceptionHandler extends BaseController implements HandlerExceptionResolver {
private static Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);

private ServiceError serviceError = new ServiceError();

@ExceptionHandler(HttpClientErrorException.class)
@ResponseBody
public OpenApiResponse httpClientErrorException(HttpServletRequest request, HttpServletResponse response, HttpClientErrorException ex){
    logger.error("requestId:{}  httpClientErrorException:{}",getRequestId(request),ex);
    OpenApiResponse res = new OpenApiResponse(getRequestId(request));
    String msg = ex.getResponseBodyAsString();
    if(StringUtils.isNotBlank(msg)){
        try{
            JSONObject jsonObject = JSON.parseObject(msg);
            int code = ex.getStatusCode().equals(HttpStatus.OK) ? 402 :  ex.getStatusCode().value();
            String status = jsonObject.getString("code");
            String message = jsonObject.getString("message");
            Map<String, String> detail = JsonUtils.jsonObject2Map(jsonObject.getJSONObject("details"));
            serviceError.setCode(code);
            serviceError.setMessage(StringUtils.isNotBlank(message) ? message : "内部调用错误");
            serviceError.setStatus(StringUtils.isNotBlank(status) ? status : "INVOKE_ERROR");
            if (detail != null) {
                serviceError.setDetails(ArrayUtils.toArray(detail));
            }
            res.setError(serviceError);
            response.setStatus(code);
            return res;
        }
        catch (Exception e){
            logger.error("解析错误,",e);
        }
    }
    serviceError.setCode(500);
    serviceError.setMessage("内部调用错误");
    serviceError.setStatus("INNER_ERROR");
    response.setStatus(500);
    res.setError(serviceError);
    return res;
}

@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public OpenApiResponse defaultException(HttpServletRequest request,HttpServletResponse response, Exception e){
    logger.error("requestId:{}  spring捕获运行时异常:{}",getRequestId(request),e);
    OpenApiResponse res = new OpenApiResponse(getRequestId(request));
    ServiceError serviceError = new ServiceError();
    serviceError.setCode(500);
    serviceError.setMessage("内部错误");
    serviceError.setStatus("INNER_ERROR");
    res.setError(serviceError);
    response.setStatus(500);
    return res;
}

@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    FastJsonJsonView fastJsonJsonView = new FastJsonJsonView();
    Map<String,Object> map = new HashMap<>();
    Map<String, Object> result = new HashMap<>();
    map.put("requestId", getRequestId(request));
    map.put("result", result);
    map.put("error", serviceError);
    fastJsonJsonView.setAttributesMap(map);
    ModelAndView mv =new ModelAndView();
    mv.setView(fastJsonJsonView);
    return mv;
}

}
1人点赞
Spring boot

# 表示前端页面的一个锚点,一般可以用来表示标题或者段落的开头,这样浏览器发现 URL 有这个锚点,就会自动滚动页面来显示这个锚点表示的内容。

# 只在浏览器生效,无法携带到后端,你可以尝试把 # 后面的内容通过 query 信息带到另一个接口,或者直接使用 Spring 提供的 RedirectAttributes 也可以。

你的这个问题属于 URL 编码问题,请求路径传输过程中会经过 URL 编码,会对一些特殊参数进行处理,Http协议中,特殊字符诸如+?%#&=/等都会被当做转义字符处理,这样的话请求路径的参数就不对了,
如果前段发起请求,后端接受
前端:
JS中用encodeURIComponent编码两次
var url = "http://localhost:8080?data=" + encodeURIComponent(encodeURIComponent(param))
后端Java解码:
String data = request.getParameter("data");
data = java.net.URLDecoder.decode(data, "UTF-8");
如果后端发起请求
需要先对请求中含有特殊字符的参数进行编码把特殊字符替换掉,比如 # 替换为 %23 ,组装后再发起请求http://ip:9143/SvgHome.html&token=aece153ba6b16a1b%23 compiles
可以使用URLEncoder.encode(参数, "UTF-8");
接受时需要对特殊的参数进行解码,比如:result = URLDecoder.decode(token, "UTF-8");

你的问题其实并不是在方法上,而是在对特殊字符转换时,把 "/" 转换为 %2F 这里出了问题,我自己实验了好多次,这个 / 转不过去,转换成%2F之后发送请求都会没有消息,其他的特殊字符进行来回转换都没有问题,

你有时间可以主要针对找一下对于 / 转换请求的问题,如果没有时间,/ 不是必要的话,还是替换了吧,这样应该可以成功

看在辛苦了一上午的份上,采纳一下吧^-^,抱歉没能侧底解决问题

在线等


public void  springbootRefresh(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        String resultUrl = "http://ip:9143/SvgHome.html&?token="+ URLEncoder.encode("aece153ba6b16a1b#/compiles", "UTF8");
        response.setHeader("refresh", "1;URL=" + resultUrl);
}

因为#被浏览器占用为关键字了,#以及后面的内容根本不会被发送出去,所以不是你收不到而是浏览器根本就没发,哈哈哈。所以你要是想约定传点啥东西就得换别的标志,为啥不当做普通key-value参数传呢?

用encode把url转码然后请求试一下

 参数是 body.getResult()
    String encode = URLEncoder.encode(参数, "UTF-8");
    String decode = URLDecoder.decode(encode, "UTF-8");