azure-maven-plugins icon indicating copy to clipboard operation
azure-maven-plugins copied to clipboard

Cannot use use byte[] when running in azure-functions-maven-plugin

Open pauldaustin opened this issue 3 years ago • 9 comments

Plugin name and version

azure-functions-maven-plugin 1.9.2

Plugin configuration in your pom.xml

App was created using archetype

 <plugin>
        <groupId>com.microsoft.azure</groupId>
        <artifactId>azure-functions-maven-plugin</artifactId>
        <version>${azure.functions.maven.plugin.version}</version>
        <configuration>
          <appName>${functionAppName}</appName>
          <resourceGroup>java-functions-group</resourceGroup>
          <appServicePlanName>java-functions-app-service-plan</appServicePlanName>
            for all valid values -->
          <region>westus</region>
          <runtime>
            <os>linux</os>
            <javaVersion>11</javaVersion>
          </runtime>
          <appSettings>
            <property>
              <name>FUNCTIONS_EXTENSION_VERSION</name>
              <value>~3</value>
            </property>
          </appSettings>
        </configuration>
        <executions>
          <execution>
            <id>package-functions</id>
            <goals>
              <goal>package</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
package demo;

import java.io.ByteArrayOutputStream;
import java.util.Optional;

import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.HttpStatus;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;

public class Echo {

  @FunctionName("Echo")
  public HttpResponseMessage run(//
    @HttpTrigger(name = "req", dataType = "binary", methods = {
      HttpMethod.POST
    }, authLevel = AuthorizationLevel.ANONYMOUS) //
    final HttpRequestMessage<Optional<byte[]>> request, //
    final ExecutionContext context) {
    try {
      final byte[] body = request.getBody().get();
      try (
        ByteArrayOutputStream out = new ByteArrayOutputStream()) {
        out.write(body);
        out.flush();
        return request.createResponseBuilder(HttpStatus.OK).body(out.toByteArray()).build();
      }
    } catch (final Throwable e) {
      return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR).body("Error").build();
    }
  }
}

Expected behavior

Works with HttpRequestMessage<Optional<byte[]>>, only works with HttpRequestMessage<Optional<String>>.

Actual behavior

[2021-03-15T19:50:00.233Z] Executed 'Functions.Echo' (Failed, Id=b78a95e8-06ff-4e4c-9299-b27065fb2bc7, Duration=133ms)
[2021-03-15T19:50:00.233Z] System.Private.CoreLib: Exception while executing function: Functions.Echo. System.Private.CoreLib: Result: Failure
[2021-03-15T19:50:00.233Z] Exception: ClassCastException: Cannot convert com.microsoft.azure.functions.worker.binding.RpcHttpRequestDataSource@112b07b8to type com.microsoft.azure.functions.HttpRequestMessage<java.util.Optional<byte[]>>
[2021-03-15T19:50:00.234Z] Stack: java.lang.ClassCastException: Cannot convert com.microsoft.azure.functions.worker.binding.RpcHttpRequestDataSource@112b07b8to type com.microsoft.azure.functions.HttpRequestMessage<java.util.Optional<byte[]>>
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.binding.DataOperations.generalAssignment(DataOperations.java:191)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.binding.DataOperations.apply(DataOperations.java:120)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.binding.DataSource.computeByType(DataSource.java:56)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.binding.RpcHttpRequestDataSource.computeByType(RpcHttpRequestDataSource.java:20)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.binding.DataSource.computeByName(DataSource.java:42)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.binding.RpcHttpRequestDataSource.computeByName(RpcHttpRequestDataSource.java:20)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.binding.BindingDataStore.getDataByName(BindingDataStore.java:55)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.broker.ParameterResolver.resolve(ParameterResolver.java:59)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.broker.ParameterResolver.resolve(ParameterResolver.java:42)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.broker.EnhancedJavaMethodExecutorImpl.execute(EnhancedJavaMethodExecutorImpl.java:53)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.broker.JavaFunctionBroker.invokeMethod(JavaFunctionBroker.java:57)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:33)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:10)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.handler.MessageHandler.handle(MessageHandler.java:45)
[2021-03-15T19:50:00.234Z] 	at com.microsoft.azure.functions.worker.JavaWorkerClient$StreamingMessagePeer.lambda$onNext$0(JavaWorkerClient.java:92)
[2021-03-15T19:50:00.234Z] 	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
[2021-03-15T19:50:00.234Z] 	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
[2021-03-15T19:50:00.234Z] 	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[2021-03-15T19:50:00.234Z] 	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[2021-03-15T19:50:00.234Z] 	at java.base/java.lang.Thread.run(Thread.java:834)

Steps to reproduce the problem

curl --request POST 'http://localhost:7071/api/Echo' --data 'hello'

pauldaustin avatar Mar 15 '21 19:03 pauldaustin

@pauldaustin Thanks for your report. @amamounelsayed Could you please help check this issue?

Flanker32 avatar Mar 16 '21 14:03 Flanker32

One further comment. It seems that if I pass in the content-type application/octet-stream in the HTTP request it will work. But other values such as application/pdf throw the error.

In my view it shouldn't be a requirement to set the content type to application/octet-stream if you want to receive the content as a byte[].

I think the Azure Function deployment also has a similar issue.

I've actually move to directly calling the blob service rather than using @BlobInput or @BlobOutput as neither of those support the use of Input/Output Streams. Which would be required to process very large files.

pauldaustin avatar Mar 18 '21 20:03 pauldaustin

@Flanker32 is there any update on this issue? I seem to be having the same problem with receiving application/pdf into a byte[] parameter.

dkontyko avatar Jun 01 '22 12:06 dkontyko

@dkontyko Sorry for the trouble, I'm syncing with the function service team and will update here once we get any updates.

Flanker32 avatar Jun 02 '22 08:06 Flanker32

@dkontyko thanks for reaching out can you provided your function code and post request that to reproduce the issue. Thank you. As mentioned by @pauldaustin the work around is to use content-type application/octet-stream in your request header. It tells functions that data type of the coming request is bytes so can processing it correctly.

kaibocai avatar Jun 03 '22 13:06 kaibocai

@kaibocai here's an example function with the POST request in the ps1 file. https://github.com/dkontyko/azure-function-bug-repro/tree/main

I tested that function locally in IntelliJ. It works if I change the Content-type to application/octet-stream, as you mentioned, but fails if it's application/pdf.

(Here's the actual repo that I was originally trying to use this on: https://github.com/dkontyko/RestPdfFormFiller, but I'm base64-encoding the file right now because I was having issues with whitespace characters getting mangled.)

dkontyko avatar Jun 04 '22 14:06 dkontyko

Hi @dkontyko thanks very much for your updates and repo. I am trying to understand that when sending pdf data are we suppose to receive them as byte[] (sorry I am not quite familiar with front-end, would you mind to share some docs on this.). Currently, if content type is set to application/pdf, the azure functions will take the request data as string type, and later when using gson to convert string to byte[], gson will failed here and cause the issue. But if it's application/octet-stream, azure functions know the request data is byte[] type and will processing it correctly with it's own type transfer logic, that's why it works in this case. Thanks for your help.

kaibocai avatar Jun 06 '22 15:06 kaibocai

@kaibocai, here's some references: https://www.iana.org/assignments/media-types/application/pdf and https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types.

PDF is a binary file type, so IMHO it should always be processed as a (binary) byte[], never a string. (I wonder if binary should be the case for all "application" types, but I don't know enough about HTTP to say that definitively. I think it would be more compliant with the RFC, though: https://datatracker.ietf.org/doc/html/rfc2046#section-4.5.3)

Thank you for looking into this.

dkontyko avatar Jun 06 '22 23:06 dkontyko

Two issues need to be fixed here:

  1. azure-function infrastructure needs to acknowledge/accept any mime types and not just e.g. application/octet-stream as binary content. suggestion: consider binary the default and only use string for a defined list of mime types like application/json etc. For http use-cases azure-functions is very limited if only few mime-types are supported (=configurable would be great)-> I guess we should open a separate issue for that, because it's not just related to java and maven.

  2. The azure-function library needs to be modified in a way it supports any mime type and provided a general ByteArray Datasource or make this one work? https://github.com/Azure/azure-functions-java-worker/blob/dev/src/main/java/com/microsoft/azure/functions/worker/binding/RpcByteArrayDataSource.java

gmuth avatar Oct 15 '23 08:10 gmuth