Не возвращает ответ downchannelStream с помощью response.body().string() после отправки https-запросов событий в AVS с использованием OkHttp в Android Studio

#android #https #okhttp #alexa #alexa-voice-service

#Android #https #okhttp #alexa #alexa-voice-service

Вопрос:

Я следую этому руководству:https://developer.amazon.com/en-US/docs/alexa/alexa-voice-service/manage-http2-connection.html

Подводя итог проблеме, я не получаю ответ downchannelStream с помощью response.body().string() после отправки запросов https на события в AVS с использованием OkHttp в Android.

Здесь я устанавливаю поток downchannel, создавая директиву http-запроса, которая должна оставаться открытой в соответствии с руководством:

 private void establishDownChanDirective(String accessToken, OkHttpClient downChannelClient) throws IOException {
    // OKHttp header creation.
    final Request getRequest = new Request.Builder()
            .url("https://alexa.na.gateway.devices.a2z.com/"   AVS_API_VERSION   "/directives")//endpoint url
            .get()
            .addHeader("authorization", "Bearer "   accessToken)
            .build();

    Log.d("Request_header", getRequest.toString());

    downChannelClient.newCall(getRequest).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.d("downChannelResp", "failure: "   e.getMessage());
            call.cancel();
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            Log.d("downChannelResp", "Down channel recieved! Test 1");
            processResponse(response, "downChannelResp", true);
            Log.d("downChannelResp", "Down channel recieved! Test 2");

            responseDirective = response;
        }
    });
}
  

Затем я пытаюсь синхронизировать с AVS, отправив событие:

 private void sendSyncEvent(OkHttpClient downChannelClient, String accessToken) throws IOException {
    String msgId = UUID.randomUUID().toString();
    String speakToken = "";
    long offsetMili = 20; // if lags put down to 10.
    String playerActivity = "PLAYING";

    final String JSON_SYNC = "{"context":[{"header":{"namespace":"SpeechRecognizer","name":"RecognizerState"},"payload":{"wakeword":"ALEXA"}},{"header":{"namespace":"SpeechSynthesizer","name":"SpeechState"},"payload":{"token":""   speakToken   "","offsetInMilliseconds":"   offsetMili   ","playerActivity":""   playerActivity   ""}}],"event":{"header":{"namespace":"System","name":"SynchronizeState","messageId":""   msgId   ""},"payload":{}}}";

    List<MultipartBody.Part> partList = new ArrayList<>();
    MultipartBody.Part syncPart = MultipartBody.Part.create(Headers.of(
            "Content-Disposition", "form-data; name="metadata""),
            RequestBody.create(JSON_SYNC, JSON_TYPE));
    partList.add(syncPart);

    RequestBody body = new MultipartBody(ByteString.encodeUtf8(BOUNDARY_TERM), MultipartBody.FORM, partList);

    Log.d("part", syncPart.headers().toString());
    Log.d("body", body.contentType().toString());

    final Request postRequest = new Request.Builder()
            .url("https://alexa.na.gateway.devices.a2z.com/" AVS_API_VERSION "/events")//endpoint url
            .post(body)
            .addHeader("authorization", "Bearer "   accessToken)
            .addHeader("content-type", "multipart/form-data; boundary="   BOUNDARY_TERM) // Don't know whether or not this is needed.
            .build();

    Log.d("post_request", postRequest.toString());
    Log.d("post_req_body", JSON_SYNC);

    downChannelClient.newCall(postRequest).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.d("syncResp", "failure: "   e.getMessage());
            call.cancel();
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            processResponse(response, "syncResp", false);
        }
    });
}
  

Затем я пытаюсь отправить событие test recognize, которое (согласно руководству) предназначено для возврата ответа через начальный downChannelStream:

 private void testRecognizeEventAVS(OkHttpClient downChannelClient, String accessToken) throws IOException {
    final MediaType AUDIO_TYPE = MediaType.parse("application/octet-stream");

    String audioMsgId = UUID.randomUUID().toString();
    String dialogId = UUID.randomUUID().toString();
    final String JSON_SPEECH_EVENT = "{"event": {"header": {"namespace": "SpeechRecognizer","name": "Recognize","messageId": ""   audioMsgId   "","dialogRequestId": ""   dialogId   ""},"payload": {"profile": "CLOSE_TALK", "format": "AUDIO_L16_RATE_16000_CHANNELS_1"}},"context": [{"header": {"namespace": "AudioPlayer","name": "PlaybackState"},"payload": {"token": "","offsetInMilliseconds": 0,"playerActivity": "FINISHED"}}, {"header": {"namespace": "SpeechSynthesizer","name": "SpeechState"},"payload": {"token": "","offsetInMilliseconds": 0,"playerActivity": "FINISHED"}}, { "header" : { "namespace" : "Alerts", "name" : "AlertsState" }, "payload" : { "allAlerts" : [ ], "activeAlerts" : [ ] } }, {"header": {"namespace": "Speaker","name": "VolumeState"},"payload": {"volume": 25,"muted": false}}]}";

    List<MultipartBody.Part> partList = new ArrayList<>();

    // Metadata Part
    Map<String, String> metaHeaders = new HashMap<String, String>();
    metaHeaders.put("Content-Disposition", "form-data; name="metadata"");
    MultipartBody.Part metaPart = MultipartBody.Part.create(Headers.of(metaHeaders), RequestBody.create(JSON_SPEECH_EVENT, JSON_TYPE));
    partList.add(metaPart);

    // Audio Part
    Map<String, String> audioHeaders = new HashMap<String, String>();
    audioHeaders.put("Content-Disposition", "form-data; name="metadata"");
    MultipartBody.Part audioPart = MultipartBody.Part.create(Headers.of(audioHeaders), RequestBody.create(createTestFile(), AUDIO_TYPE));
    partList.add(audioPart);

    RequestBody reqBody = new MultipartBody(ByteString.encodeUtf8(BOUNDARY_TERM), MultipartBody.FORM, partList);

    Log.d("metaPart", metaPart.headers().toString());
    Log.d("audioPart", audioPart.headers().toString());
    Log.d("body", reqBody.contentType().toString());

    // https://developer.amazon.com/en-US/docs/alexa/alexa-voice-service/structure-http2-request.html

    Request speechRequest = new Request.Builder()
            .url("https://alexa.na.gateway.devices.a2z.com/" AVS_API_VERSION "/events")
            .addHeader("authorization", "Bearer "   accessToken)
            .addHeader("content-type", "multipart/form-data; boundary="   BOUNDARY_TERM) // Don't know whether or not this is needed.
            .post(reqBody)
            .build();

    Log.d("speech_request", speechRequest.toString());

    downChannelClient.newCall(speechRequest).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.d("speechResp", "failure: "   e.getMessage());
            call.cancel();
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            processResponse(response, "speechResp", false);
        }
    });
}
  

This is the processResponse method used in each above methods to take the responses and output information about it to the Android log:

 private void processResponse(Response response, final String TAG, boolean readBodySource) throws IOException {
    //Log.d(TAG, "response-string: "   response.body().string()); // This never shows up and always stops the rest of this method running for the response from establishDownChanDirective().
    Log.d(TAG, "response-success: "   response.isSuccessful());
    Log.d(TAG, "response"   response.toString());

    // Tried this from stack over flow posts, but right now we aren't even receiving a response-string from the downChannelDirective, so we need to figure that out first.
    if (readBodySource) {
        BufferedSource bufferedSource = response.body().source();

        Buffer buffer = new Buffer();

        while (!bufferedSource.exhausted()) {
            Log.w("bufferedSource", "downchannel recieved!");
            long bs = bufferedSource.read(buffer, 8192);
            Log.d("bufferedSource_read", String.valueOf(bs));
            Log.d("buffersize", String.valueOf(buffer.size()));
        }

        Log.d("buffer_response", buffer.toString());
    }
}
  

Этот метод закомментировал строку-ответ, но когда он не закомментирован, он просто выдает D/syncResp: response-string: в качестве выходных данных, где строка ответа — это просто пустая строка для syncResp и speechResp. Однако для downChannelResp он ничего не выдает в качестве выходных данных и полностью останавливает выполнение остальной части приведенного ниже кода Log.d(TAG, "response-string: " response.body().string()); .

Теперь, когда я запускаю это…

 try {
    establishDownChanDirective(accessToken, downChannelClient); // Establish a down channel directive that will remain open.
    sendSyncEvent(downChannelClient, accessToken); // Send a Syncronize event through the same connection as the down channel directive.
    testRecognizeEventAVS(downChannelClient, accessToken); // Send a Speech directive through the same connection as the down channel directive.
    Log.d("OkHttp", "Test: Http stuff finished.");
    if (responseDirective != null) {
        Log.d("OkHttp", "Response: "   responseDirective.body().string());
    } else {
        Log.d("OkHttp", "No response!");
    }
} catch (IOException e) {
    Log.d("OkHttpError", "error: START{"   e.toString()   "}END");
    e.printStackTrace();
}
  

…он выдает это в качестве вывода:

 D/Request_header: Request{method=GET, url=https://alexa.na.gateway.devices.a2z.com/v20160207/directives, headers=[authorization:Bearer <the access token - censored for this post>]}
D/part: Content-Disposition: form-data; name="metadata"
D/body: multipart/form-data; boundary=------------------------qM9tn4VZyj
D/post_request: Request{method=POST, url=https://alexa.na.gateway.devices.a2z.com/v20160207/events, headers=[authorization:Bearer <the access token - censored for this post>, content-type:multipart/form-data; boundary=------------------------qM9tn4VZyj]}
D/post_req_body: {"context":[{"header":{"namespace":"SpeechRecognizer","name":"RecognizerState"},"payload":{"wakeword":"ALEXA"}},{"header":{"namespace":"SpeechSynthesizer","name":"SpeechState"},"payload":{"token":"","offsetInMilliseconds":20,"playerActivity":"PLAYING"}}],"event":{"header":{"namespace":"System","name":"SynchronizeState","messageId":"2c46b1a9-8b41-47be-bd09-61166b78492e"},"payload":{}}}
D/parent: /storage/emulated/0/Android/data/aut.rnd.alexa/files
D/fileexists: true
D/media_file: successfully created: true
D/metaPart: Content-Disposition: form-data; name="metadata"
D/audioPart: Content-Disposition: form-data; name="metadata"
D/body: multipart/form-data; boundary=------------------------qM9tn4VZyj
D/speech_request: Request{method=POST, url=https://alexa.na.gateway.devices.a2z.com/v20160207/events, headers=[authorization:Bearer <the access token - censored for this post>, content-type:multipart/form-data; boundary=------------------------qM9tn4VZyj]}
D/OkHttp: Test: Http stuff finished.
    No response!
D/downChannelResp: Down channel recieved! Test 1
    response-success: true
    responseResponse{protocol=h2, code=200, message=, url=https://alexa.na.gateway.devices.a2z.com/v20160207/directives}
W/bufferedSource: downchannel recieved!
D/bufferedSource_read: 18
D/buffersize: 18
D/syncResp: response-success: true
    responseResponse{protocol=h2, code=204, message=, url=https://alexa.na.gateway.devices.a2z.com/v20160207/events}
D/speechResp: response-success: true
    responseResponse{protocol=h2, code=204, message=, url=https://alexa.na.gateway.devices.a2z.com/v20160207/events}
  

Это неожиданно, потому что ответ должен возвращать данные, которые могут быть преобразованы в JSON, но, похоже, он вообще ничего не возвращает.

Ответ №1:

Это может быть вашей проблемой. https://square.github.io/okhttp/4.x/okhttp/okhttp3/-response-body/#the-response-body-can-be-consumed-only-once

Тело ответа может быть использовано только один раз.

Этот класс может использоваться для потоковой передачи очень больших ответов. Например, можно использовать этот класс для чтения ответа, размер которого превышает всю память, выделенную текущему процессу. Он может даже передавать ответ, превышающий общий объем памяти на текущем устройстве, что является общим требованием для приложений потокового видео.

Комментарии:

1. Я попытался вызвать его только один раз, и, к сожалению, он по-прежнему не дает никакого ответа.