Как получить байт [] (изображение) из веб-службы с помощью микронавта HttpClient

Я портирую библиотеку Grails 3.1 для использования некоторых внутренних веб-сервисов на Grails 4.0. Одна из служб предоставляет изображение запрашиваемого сотрудника по запросу. У меня возникают трудности с реализацией кода (micronaut) HttpClient для обработки запроса, в частности, для получения правильного байта [], который является возвращаемым изображением.

Простая команда curl в командной строке работает со службой:

curl -D headers.txt -H 'Authorization:Basic <encodedKeyHere>' https:<serviceUrl> >> image.jpg

и изображение правильное. Это header.txt:

HTTP/1.1 200 
content-type: image/jpeg;charset=UTF-8
date: Tue, 27 Aug 2019 20:05:43 GMT
x-ratelimit-limit: 100
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
x-ratelimit-remaining: 99
X-RateLimit-Reset: 38089
x-ratelimit-reset: 15719
Content-Length: 11918
Connection: keep-alive

Старая библиотека использует groovyx.net.http.HTTPBuilder и просто делает:

http.request(Method.GET, ContentType.BINARY) {
            uri.path = photoUrlPath
            uri.query = queryString
            headers.'Authorization' = "Basic $encoded".toString()
            response.success = { resp, inputstream ->
                log.info "response status: ${resp.statusLine}"                
                return ['status':resp.status, 'body':inputstream.getBytes()]
            }
            response.failure = { resp ->
                return ['status':resp.status, 
                        'error':resp.statusLine.reasonPhrase, 
                         body:resp.getEntity().getContent().getText()]
            }
        }

поэтому возвращаем байты из inputStream. Это работает.

Я пробовал несколько вещей, используя микронавт HttpClient, как с API низкого уровня, так и с декларативным API. Простой пример с декларативным API:


    @Get(value='${photo.ws.pathurl}', produces = MediaType.IMAGE_JPEG)
    HttpResponse<byte[]> getPhoto(@Header ('Authorization') String authValue, 
                                  @QueryValue("emplId") String emplId)

А чем в Сервисе:

    HttpResponse<byte[]> resp = photoClient.getPhoto(getBasicAuth(),emplId)
    def status = resp.status()            // code == 200 --> worked 
    def bodyStrOne = resp.getBody()       // nope: get Optional.empty 
    // Tried different getBody(class) -> Can't figure out where the byte[]s are   
    // For example can do:
    def buf = resp.getBody(io.netty.buffer.ByteBuf).value // Why need .value?
    def bytes = buf.readableBytes()       // Returns 11918 --> the expected value
    byte[] ans = new byte[buf.readableBytes()]
    buf.readBytes(ans)    // Throws exception: io.netty.util.IllegalReferenceCountException: refCnt: 0

Это "работает", но возвращенная строка теряет некоторую кодировку, которую я не могу изменить:

   // Client - use HttpResponse<String>
   @Get(value='${photo.ws.pathurl}', produces = MediaType.IMAGE_JPEG)
   HttpResponse<String> getPhoto(@Header ('Authorization') String authValue, 
                                  @QueryValue("emplId") String emplId)
   // Service 
   HttpResponse<String> respOne = photoClient.getPhoto(getBasicAuth(),emplId)
   def status = respOne.status()                  // code == 200 --> worked 
   def bodyStrOne = respOne.getBody(String.class) // <-- RETURNS DATA..just NOT an Image..or encoded or something
  String str = bodyStrOne.value                   // get the String data  
  // But these bytes aren't correct
  byte[] ans = str.getBytes()                     // NOT an image..close but not.
  // str.getBytes(StandardCharsets.UTF_8) or any other charset doesn't work 

Все, что я пробовал с классами ByteBuf, выдает исключение io.netty.util.IllegalReferenceCountException: refCnt: ​​0.

Любое направление / помощь будет принята с благодарностью.

Бег:

    Grails 4.0 
    JDK    1.8.0_221 
    Groovy   2.4.7
    Windows  10
    IntellJ  2019.2  

3 ответа

Должно быть, это ошибка Grails.


Добавьте эту строку в logback.groovy:

logger("io.micronaut.http", TRACE)

Затем вы должны увидеть, что тело не было пустым, но, наконец, оно заканчивается ошибкой Unable to convert response body to target type class [B. Смотрите след:

2019-09-11 11:19:16.235 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient        : Status Code: 200 OK
2019-09-11 11:19:16.235 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient        : Content-Type: image/jpeg
2019-09-11 11:19:16.235 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient        : Content-Length: 11112
2019-09-11 11:19:16.237 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient        : Accept-Ranges: bytes
2019-09-11 11:19:16.237 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient        : Response Body
2019-09-11 11:19:16.237 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient        : ----
2019-09-11 11:19:16.238 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient        : ���� C   
...
2019-09-11 11:19:16.241 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient        : ----
2019-09-11 11:19:16.243 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient        : Unable to convert response body to target type class [B

Но когда вы попробуете то же самое в отдельном приложении Microunaut (добавьте <logger name="io.micronaut.http" level="trace"/> в logback.xml) результат другой:

09:02:48.583 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - Status Code: 200 OK
09:02:48.583 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - Content-Type: image/jpeg
09:02:48.589 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - content-length: 23195
09:02:48.590 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - Response Body
09:02:48.590 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - ----
09:02:48.612 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - ���� C���
...
09:02:48.620 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - ----

Трассировка микронавта не содержит ошибок.


Вот пример декларативного HTTP-клиента, который загружает случайное изображение с веб-сайта https://picsum.photos/:

import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client

@Client('https://picsum.photos')
interface LoremPicsumClient {
    @Get(value = '{width}/{height}', produces = MediaType.IMAGE_JPEG)
    HttpResponse<byte[]> getImage(Integer width, Integer height)
}

И юнит-тест Spock для этого:

import io.micronaut.http.HttpStatus
import io.micronaut.test.annotation.MicronautTest
import spock.lang.Specification

import javax.inject.Inject
import java.nio.file.Files
import java.nio.file.Paths

@MicronautTest
class LoremPicsumClientSpec extends Specification {
    @Inject
    LoremPicsumClient client

    void 'image is downloaded'() {
        given:
        def output = Paths.get('test')

        when:
        def response = client.getImage(300, 200)

        then:
        response.status == HttpStatus.OK
        response.getBody().isPresent()

        when:
        Files.write(output, response.getBody().get())

        then:
        Files.probeContentType(output) == 'image/jpeg'
    }
}

В Micronaut тест проходит, и изображение сохраняется в тестовом файле. Но в Grails тест не проходит, потому что HttpClient не может преобразовать байты ответа вbyte массив или лучше во что-нибудь еще String.

Документация предлагает отправлять байты через входной поток, но мне не удалось заставить это работать. Самое хрупкое, что HttpClient должен Потреблять байты, а Сервер должен Производить .

      @Get(value = "/write", produces = MediaType.TEXT_PLAIN)
HttpResponse<byte[]> write() {
    byte[] bytes = "test".getBytes(StandardCharsets.UTF_8);
    return HttpResonse.ok(bytes); // 
}

В настоящее время мы используем эту реализацию:

@Client(value = "\${image-endpoint}")
interface ImageClient {

  @Get("/img")
  fun getImageForAddress(
    @QueryValue("a") a: String
  ): CompletableFuture<ByteArray>
}

у нас отлично работает.

Когда я использую HttpResponse, я тоже получаю сообщение об ошибке, не могу заставить его работать с этим.

Другие вопросы по тегам