Как получить байт [] (изображение) из веб-службы с помощью микронавта 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, я тоже получаю сообщение об ошибке, не могу заставить его работать с этим.