溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringBoot RestTemplate 下載圖片時url被轉義的問題解決

發(fā)布時間:2020-09-10 20:03:30 來源:網(wǎng)絡 閱讀:794 作者:微然月明 欄目:編程語言

問題:

在實際項目中,對傳入的圖片url進行下載,使用的是RestTemplate的exchange方法,具體如下:

使用以下RestTemplate的方法:

Sring url = "http://10.64.203.183:6120/pic?=d6ei2a4i9c84*33c-793=11i5m*ep5t9d5=*2pdi=*1s5i2=94b8i5d2e*14b863328-aa2e959-1b246b-43s=10d3z83";
ResponseEntity<byte[]> responseEntity = restTemplate
                .exchange(url, HttpMethod.GET, null, byte[].class);

用這種方式調(diào)用請求,抓拍可看出url中的特殊字符被轉義:

http://10.64.203.183:6120/pic?d6ei2a4i9c84*33c-793=11i5m*ep5t9d5%3D*2pdi%3D*1s5i2%3D94b8i5d2e*14b863328-aa2e959-1b246b-43s%3D10d3z83

由于第三方的圖片服務器,沒有對請求的url進行解碼,因此不識別轉義后的url導致下載失敗。

解決方法:

如果不希望被轉碼,則可使用
public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType)
該方法,直接傳入URI對象。
該對象可如下進行組裝:

Sring url = "http://10.64.203.183:6120/pic?=d6ei2a4i9c84*33c-793=11i5m*ep5t9d5=*2pdi=*1s5i2=94b8i5d2e*14b863328-aa2e959-1b246b-43s=10d3z83";
URI uri = new URI(url);
restTemplate.exchange(url, HttpMethod.GET, null, byte[].class);

這樣,url就不會被encode成utf-8,不會將特殊字符轉義,解決了下載失敗的問題。

源碼分析

以下是url被轉碼的源碼分析。

對應的包為

Sring url = "http://10.64.203.183:6120/pic?=d6ei2a4i9c84*33c-793=11i5m*ep5t9d5=*2pdi=*1s5i2=94b8i5d2e*14b863328-aa2e959-1b246b-43s=10d3z83";
ResponseEntity<byte[]> responseEntity = restTemplate
                .exchange(url, HttpMethod.GET, null, byte[].class);

對應源碼的調(diào)用關系:

RestTemplate.class:

public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
        RequestCallback requestCallback = this.httpEntityCallback(requestEntity, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(responseType);
        return (ResponseEntity)nonNull(this.execute(url, method, requestCallback, responseExtractor, uriVariables));
    }
RestTemplate.class:

@Nullable
    public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables) throws RestClientException {
        URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);
        return this.doExecute(expanded, method, requestCallback, responseExtractor);
    }

從execute函數(shù)可看出,源碼中會將url String對象轉成URI對象。

轉換的源碼如下:

DefaultUriBuilderFactory.class:

public URI expand(String uriTemplate, Map<String, ?> uriVars) {
        return this.uriString(uriTemplate).build(uriVars);
    }

我們來看下this.uriString(uriTemplate),返回的是UriBuilder 對象,


public UriBuilder uriString(String uriTemplate) {
        return new DefaultUriBuilderFactory.DefaultUriBuilder(uriTemplate);
    }

private class DefaultUriBuilder implements UriBuilder:

public DefaultUriBuilder(String uriTemplate) {
            this.uriComponentsBuilder = this.initUriComponentsBuilder(uriTemplate);
        }

private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
            UriComponentsBuilder result;
            if (!StringUtils.hasLength(uriTemplate)) {
                result = DefaultUriBuilderFactory.this.baseUri != null ? DefaultUriBuilderFactory.this.baseUri.cloneBuilder() : UriComponentsBuilder.newInstance();
            } else if (DefaultUriBuilderFactory.this.baseUri != null) {
                UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate);
                UriComponents uri = builder.build();
                result = uri.getHost() == null ? DefaultUriBuilderFactory.this.baseUri.cloneBuilder().uriComponents(uri) : builder;
            } else {
                result = UriComponentsBuilder.fromUriString(uriTemplate);
            }

            if (DefaultUriBuilderFactory.this.encodingMode.equals(DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES)) {
                result.encode();
            }

            this.parsePathIfNecessary(result);
            return result;
        }

result.encode();該語句設置了該對象編碼格式:

UriComponentsBuilder.class
public final UriComponentsBuilder encode() {
        return this.encode(StandardCharsets.UTF_8);
    }

public UriComponentsBuilder encode(Charset charset) {
        this.encodeTemplate = true;
        this.charset = charset;
        return this;
    }

至此,獲取到一個UriBuilder對象,且該對象中的charset設置成utf-8。
以上代碼,我們解釋到了this.uriString(uriTemplate),返回的是UriBuilder 對象,接下來我們看this.uriString(uriTemplate).build(uriVars)中build(uriVars)。

UriComponentsBuilder.class

public URI build(Map<String, ?> uriVariables) {

        return this.buildInternal(UriComponentsBuilder.EncodingHint.ENCODE_TEMPLATE).expand(uriVariables).toUri();
    }

/**
被上以函數(shù)調(diào)用,傳入的hint是UriComponentsBuilder.EncodingHint.ENCODE_TEMPLATE
*/
private UriComponents buildInternal(UriComponentsBuilder.EncodingHint hint) {
        Object result;
        if (this.ssp != null) {
            result = new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
        } else {
            //走到該邏輯
            HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment, this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, hint == UriComponentsBuilder.EncodingHint.FULLY_ENCODED);
            //下面的判斷為真,因此result賦值為uric.encodeTemplate(this.charset),其中this.charset在上面已被設置成utf-8
            result = hint == UriComponentsBuilder.EncodingHint.ENCODE_TEMPLATE ? uric.encodeTemplate(this.charset) : uric;
        }

        if (!this.uriVariables.isEmpty()) {
            result = ((UriComponents)result).expand((name) -> {
                return this.uriVariables.getOrDefault(name, UriTemplateVariables.SKIP_VALUE);
            });
        }

        return (UriComponents)result;
    }

HierarchicalUriComponents.class

/**
該對象將對象中的內(nèi)容均進行utf-8編碼
*/
HierarchicalUriComponents encodeTemplate(Charset charset) {
        if (this.encodeState.isEncoded()) {
            return this;
        } else {
            this.variableEncoder = (value) -> {
                return encodeUriComponent(value, charset, HierarchicalUriComponents.Type.URI);
            };
            HierarchicalUriComponents.UriTemplateEncoder encoder = new HierarchicalUriComponents.UriTemplateEncoder(charset);
            String schemeTo = this.getScheme() != null ? encoder.apply(this.getScheme(), HierarchicalUriComponents.Type.SCHEME) : null;
            String fragmentTo = this.getFragment() != null ? encoder.apply(this.getFragment(), HierarchicalUriComponents.Type.FRAGMENT) : null;
            String userInfoTo = this.getUserInfo() != null ? encoder.apply(this.getUserInfo(), HierarchicalUriComponents.Type.USER_INFO) : null;
            String hostTo = this.getHost() != null ? encoder.apply(this.getHost(), this.getHostType()) : null;
            HierarchicalUriComponents.PathComponent pathTo = this.path.encode(encoder);
            MultiValueMap<String, String> queryParamsTo = this.encodeQueryParams(encoder);
            return new HierarchicalUriComponents(schemeTo, fragmentTo, userInfoTo, hostTo, this.port, pathTo, queryParamsTo, HierarchicalUriComponents.EncodeState.TEMPLATE_ENCODED, this.variableEncoder);
        }
    }

如上,因此調(diào)用RestTemplate類的public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType)函數(shù),傳入的url會被轉碼成utf-8。

這樣,傳輸過程中特殊字符就不會被轉義

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI