microsoft / microsoft/azure-container-apps

Feature Request: Websockets and gRPC support are mutually exclusive (ingress transport 'auto' is 'http')

Open
#562 11 comments 9 reactions 1 assignee View on GitHub

@cachai2 is already working on this.

Since Mar 6, 2023.

ARM/Bicep enhancement investigating Networking
Dominant language
Bicep
Stars
445
Forks
54
Avg merge
5h 14m
Merged PRs (30d)
3

Description

Please provide us with the following information:

This issue is a: (mark with an x)
  • bug report -> please search issues before submitting
  • documentation issue or request
  • regression (a behavior that used to work and stopped in a new release)
Issue description

We want to accept WebSocket connections and gRPC requests, on one container app. This is currently impossible, because it seems that transport: auto does not automatically detect HTTP/1 or HTTP/2. transport: auto seems to be equal to transport: http.

Steps to reproduce

Spin up a container app with transport: auto with Websocket and gRPC handlers.

Expected behavior
Setting transport: auto detects HTTP/2 for gRPC and HTTP/1 for Websockets.

Actual behavior
Setting transport: auto or http means only Websockets work and setting transport: http2 means only gRPC works.

Additional context

Docs

The docs here explain the possible values for ingress.transport:

http for HTTP/1, http2 for HTTP/2, auto to automatically detect HTTP/1 or HTTP/2 (default), tcp for TCP.

Those docs also indicate that

With HTTPS ingress enabled, your container app … supports WebSocket and gRPC.

Related issues

It is well documented that gRPC requires transport: http2. This issue for example: https://github.com/microsoft/azure-container-apps/issues/38.

It is also the case that Websocket requires transport: auto or http. See our tests below and this issue: https://github.com/microsoft/azure-container-apps/issues/280.

In fact, as @ahmelsayed suggests here and here, it seems that auto just means http.

Our own testing

We ran some of our own testing, to reproduce the behaviour described here: https://github.com/microsoft/azure-container-apps/issues/280.
This involved deploying a minimal websockets server in Go and changing the ingress transport settings.
We found that it indeed worked with either transport: auto or http.
But with transport: http2, the client gives the error failed to WebSocket dial: expected handshake response status code 101 but got 503.
Of course, that makes sense because our toy websockets handler doesn't accept http2, but is a problem because our real app needs transport: http2 for gRPC.

If it helps, we could write a minimal server that accepts both Websockets and HTTP/2 (e.g. gRPC), to show that only HTTP/1 works with auto.

Our code

main.bicep

resource containerApp 'Microsoft.App/containerApps@2022-01-01-preview' = {
  name: appName
  location: location

  identity: {
    type: 'SystemAssigned'
  }

  properties: {
    managedEnvironmentId: environment.id
    configuration: {  
      ingress: {
        external: true
        targetPort: 9090
        transport: 'http2' // doesn't work
        //transport: 'http' // works
        //transport: 'auto' // works
      }
      secrets: [
        {
          name: 'container-registry-password'
          value: registry.listCredentials().passwords[0].value
        }
      ]
      registries: [
        {
          server: '${registryName}.azurecr.io'
          username: registry.listCredentials().username
          passwordSecretRef: 'container-registry-password'
        }
      ]
    }
    template: {
      containers: [
        {
          image: containerImage
          name: appName
          resources: {
            cpu: json('0.5') // https://github.com/Azure/bicep/issues/5993
            memory: '1Gi'
          }
        }
      ]
    }
    
  }
}

server.go

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"

	"nhooyr.io/websocket"
	"nhooyr.io/websocket/wsjson"
)

func main() {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		c, err := websocket.Accept(w, r, nil)
		if err != nil {
			panic(fmt.Sprintf("Accept failed: %v", err))
		}
		defer c.Close(websocket.StatusInternalError, "Internal error")

		ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
		defer cancel()

		var v interface{}
		err = wsjson.Read(ctx, c, &v)
		if err != nil {
			panic(fmt.Sprintf("JSON read failed: %v", err))
		}

		log.Printf("received: %v", v)

		c.Close(websocket.StatusNormalClosure, "")
	})

	port := "9090"
	addr := fmt.Sprintf("0.0.0.0:%s", port)
	log.Printf("Listening on port %s", port)

	err := http.ListenAndServe(addr, handler)
	panic(fmt.Sprintf("Listening error: %v", err))
}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.