grpc / grpc-swift

The Swift language implementation of gRPC.
Apache License 2.0
2.01k stars 416 forks source link

Try to send multiple files throw grpc #1679

Closed DrikABrak closed 11 months ago

DrikABrak commented 11 months ago

What are you trying to achieve?

Hi, i'm new in grpc-swift and i would like to send several documents (data) but i have an error. Is it a good way to do it or i missing something ? Here is my proto (with oneof for SendDocumentInfos):

rpc SendDocument(stream SendDocumentInfos) returns (SendDocumentStatus);

message SendDocumentInfos {
    oneof data {
      // Contenu du document
      bytes content = 1;

      // Type du document (permis de conduire, assurance, ...)
      DocumentType type = 2;

      // Partie envoyée
      int32 part = 3;

      // MIME type du document envoyé
      string contentType = 4;

      // Titre du document
      string title = 5;
  }
}

What have you tried so far?

And here is my function to send documents that i try:

func sendDocument(datas: [Data]) -> Authenticate_SendDocumentStatus? {

        let result: Authenticate_SendDocumentStatus

        let stream = getAuthenticateClient().sendDocument(callOptions: GRPCClientService.shared.callOptions)

        var requests: [Authenticate_SendDocumentInfos] = []
        var part = 1
        for data in datas {
            let docRequest: Authenticate_SendDocumentInfos = .with {
                $0.title = "driving_licence_label".localize()
                $0.type = .drivingLicense
                $0.content = data.base64EncodedData()
                $0.part = Int32(part)
                $0.contentType = data.mimeType
            }
            requests.append(docRequest)
            part += 1
        }
        _ = stream.sendMessages(requests)
        _ = stream.sendEnd()

        do {
            result = try stream.response.wait()
        } catch {
            print("RPC method ‘sendDocument’ failed: \(error)")
            return nil
        }
        return result
}

And here is my log:

2023-10-19T17:43:07+0200 trace io.grpc : call_state=awaitingTransport (0 parts buffered) grpc_connection_id=A1BD6825-315D-4F5D-A29C-6071393F5829/0 grpc_request_id=1B3E6C23-5C36-4027-8CFC-A8FC8172AF4A request_part=metadata [GRPC] buffering request part
2023-10-19T17:43:07+0200 debug io.grpc : grpc.conn.addr_local=10.20.3.44 grpc.conn.addr_remote=51.38.36.156 grpc_connection_id=A1BD6825-315D-4F5D-A29C-6071393F5829/0 grpc_request_id=1B3E6C23-5C36-4027-8CFC-A8FC8172AF4A [GRPC] activated stream channel
2023-10-19T17:43:07+0200 trace io.grpc : grpc.conn.addr_local=10.20.3.44 grpc.conn.addr_remote=51.38.36.156 grpc_connection_id=A1BD6825-315D-4F5D-A29C-6071393F5829/0 grpc_request_id=1B3E6C23-5C36-4027-8CFC-A8FC8172AF4A request_parts=1 [GRPC] unbuffering request parts
2023-10-19T17:43:07+0200 trace io.grpc : grpc.conn.addr_local=10.20.3.44 grpc.conn.addr_remote=51.38.36.156 grpc_connection_id=A1BD6825-315D-4F5D-A29C-6071393F5829/0 grpc_request_id=1B3E6C23-5C36-4027-8CFC-A8FC8172AF4A request_part=metadata [GRPC] unbuffering request part
2023-10-19T17:43:07+0200 trace io.grpc : grpc_connection_id=A1BD6825-315D-4F5D-A29C-6071393F5829/0 grpc_request_id=1B3E6C23-5C36-4027-8CFC-A8FC8172AF4A h2_end_stream=false h2_headers=[([], ":method", "POST"), ([], ":path", "/authenticate.Authentication/SendDocument"), ([], ":authority", "grpc.io"), ([], ":scheme", "https"), ([], "content-type", "application/grpc"), ([], "te", "trailers"), ([], "authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey"), ([], "user-agent", "grpc-swift-nio/1.19.1")] h2_payload=HEADERS [GRPC] writing HTTP2 frame
2023-10-19T17:43:07+0200 trace io.grpc : grpc.conn.addr_local=10.20.3.44 grpc.conn.addr_remote=51.38.36.156 grpc_connection_id=A1BD6825-315D-4F5D-A29C-6071393F5829/0 grpc_request_id=1B3E6C23-5C36-4027-8CFC-A8FC8172AF4A [GRPC] request buffer drained
2023-10-19T17:43:07+0200 trace io.grpc : grpc_connection_id=A1BD6825-315D-4F5D-A29C-6071393F5829/0 grpc_request_id=1B3E6C23-5C36-4027-8CFC-A8FC8172AF4A h2_data_bytes=7 h2_end_stream=false h2_payload=DATA [GRPC] writing HTTP2 frame
2023-10-19T17:43:07+0200 trace io.grpc : grpc_connection_id=A1BD6825-315D-4F5D-A29C-6071393F5829/0 grpc_request_id=1B3E6C23-5C36-4027-8CFC-A8FC8172AF4A h2_data_bytes=0 h2_end_stream=true h2_payload=DATA [GRPC] writing HTTP2 frame
2023-10-19T17:43:07+0200 trace io.grpc : grpc_connection_id=A1BD6825-315D-4F5D-A29C-6071393F5829/0 grpc_request_id=1B3E6C23-5C36-4027-8CFC-A8FC8172AF4A h2_end_stream=true h2_headers=[([], ":status", "200"), ([], "content-type", "application/grpc"), ([], "date", "Thu, 19 Oct 2023 15:43:07 GMT"), ([], "server", "Kestrel"), ([non-indexable], "content-length", "0"), ([], "grpc-status", "2"), ([], "grpc-message", "Exception was thrown by handler. NullReferenceException: Object reference not set to an instance of an object.")] h2_payload=HEADERS [GRPC] received HTTP2 frame
success(unknown (2): Exception was thrown by handler. NullReferenceException: Object reference not set to an instance of an object.)
RPC method ‘sendDocument’ failed: unknown (2): Exception was thrown by handler. NullReferenceException: Object reference not set to an instance of an object.

Thanks in advance for all your advices and solutions.

Lukasa commented 11 months ago

Is the content expected to be base64 encoded?

Either way, this appears to be an unhelpful error sent by the Java side.

DrikABrak commented 11 months ago

Hi @Lukasa, Thanks for your answer. Yes the content should be base64 encoded. It's image files. I don't understand this error but it doesn't work.

But if my code seems to be correct or not? I have a doubt on oneof structure.

Thanks

Lukasa commented 11 months ago

Oh yeah, good catch I didn't notice that. You should only set one of those. It's hard to tell from the API but you probably have to send those in a specific order

DrikABrak commented 11 months ago

What did youmean by 'You should only set one of those' ? I have an old code which use Combine and seems to work but i'd like to remove Combine. The only difference is that in old cold, data is sent by chunks but i don't think that it is my problem.

        let coldObs = datas.enumerated().publisher
            .buffer(size: 20, prefetch: .keepFull, whenFull: .dropOldest)
            .flatMap(maxPublishers: .max(1)) { (index, pageData) -> AnyPublisher<Authenticate_SendDocumentInfos, Error> in
                let base64EncodedData = pageData.base64EncodedData()
                let chunks = base64EncodedData.chunk(chunkSize: 1000).map{ chunk in
                    Authenticate_SendDocumentInfos.with { $0.content = chunk}
                }

                let tmp = [
                    Authenticate_SendDocumentInfos.with {$0.part = Int32(index + 1) },
                    Authenticate_SendDocumentInfos.with { $0.contentType = mimeTypes[safe:index] ?? mimeTypes[0]},
                ] + chunks

                return tmp.publisher.setFailureType(to: Error.self).eraseToAnyPublisher()
            }
            .prepend(Authenticate_SendDocumentInfos.with { $0.type = documentType })
            .prepend(Authenticate_SendDocumentInfos.with { $0.title = documentName })
            .eraseToAnyPublisher()
DrikABrak commented 11 months ago

So my problem is finally resolved! Like @Lukasa said before, we should send each parameter one by one in a specific order.

Here is the right code:

    /// Send a document
    func sendDocument(docs: [Data]) -> Authenticate_SendDocumentStatus? {

        let result: Authenticate_SendDocumentStatus

        var logger = Logger(label: "io.grpc")
        logger.logLevel = .trace
        let callOptions = CallOptions(customMetadata: GRPCClientService.shared.callOptions!.customMetadata, logger: logger)
        let stream = getAuthenticateClient().sendDocument(callOptions: callOptions)

        var docRequest: Authenticate_SendDocumentInfos = .with { $0.title = "driving_licence_label".localize() }
        _ = stream.sendMessage(docRequest)

        docRequest = .with { $0.type = .drivingLicense }
        _ = stream.sendMessage(docRequest)

        var part = 1
        for doc in docs {
            docRequest = .with { $0.part = Int32(part) }
            _ = stream.sendMessage(docRequest)

            docRequest = .with { $0.contentType = doc.mimeType }
            _ = stream.sendMessage(docRequest)

            docRequest = .with { $0.content = doc.base64EncodedData() }
             _ = stream.sendMessage(docRequest)

            part += 1
        }

        _ = stream.sendEnd()

        do {
            result = try stream.response.wait()
        } catch {
            print("RPC method ‘sendDocument’ failed: \(error)")
            return nil
        }

        return result
    }

Thanks.