Go语言Azure Function部署到Azure后执行失败,返回500内部服务器错误
Azure Function部署后返回500错误排查与修复
我用Go语言开发了一款Azure Function,功能为读取Azure存储账户中的内容并将数据发送至其他服务。该函数在本地环境运行正常,但部署到Azure后,调用时耗时较长且持续返回500内部服务器错误。相关代码及配置文件如下:
核心代码
package main import ( "bytes" "context" "encoding/csv" "fmt" "io" "log" "net/http" "os" "time" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" "github.com/gin-gonic/gin" "github.com/joho/godotenv" ) func handleError(err error) { if err != nil { log.Fatal(err.Error()) } } func get_Port() string { port := ":8000" if val, ok := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT"); ok { port = ":" + val } return port } func handleFunc() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Set("Content-Type", "application/json") c.Writer.Header().Set("Access-Control-Allow-Origin", "https://portal.azure.com.") // 使用请求上下文替代Background,避免超时问题 if err := postFunc(c.Request.Context()); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "body": "success", }) } } func main() { // 仅在本地加载.env文件 if os.Getenv("AZURE_FUNCTIONS_ENVIRONMENT") != "Production" { err := godotenv.Load() if err != nil { log.Println("Warning: 未加载.env文件") } } router := gin.Default() router.GET("/api/Ziyad", handleFunc()) port := get_Port() router.Run(port) } // 修改为返回错误,避免直接终止进程 func postFunc(ctx context.Context) error { fmt.Printf("Azure Blob storage quick start sample\n") // 替换为实际存储账户名 url := "https://{mystorageaccountname}.blob.core.windows.net/" credential, err := azidentity.NewDefaultAzureCredential(nil) if err != nil { log.Printf("获取Azure凭据失败: %v", err) return fmt.Errorf("获取Azure凭据失败: %w", err) } client, err := azblob.NewClient(url, credential, nil) if err != nil { log.Printf("创建Blob客户端失败: %v", err) return fmt.Errorf("创建Blob客户端失败: %w", err) } fmt.Println("列出容器中的Blob:") containerName := "bcfolder" blobName := "Book1.csv" blobDownloadResp, err := client.DownloadStream(ctx, containerName, blobName, nil) if err != nil { log.Printf("下载Blob流失败: %v", err) return fmt.Errorf("下载Blob流失败: %w", err) } defer blobDownloadResp.Body.Close() downloadedData, err := io.ReadAll(blobDownloadResp.Body) if err != nil { log.Printf("读取Blob数据失败: %v", err) return fmt.Errorf("读取Blob数据失败: %w", err) } csvData := csv.NewReader(bytes.NewBuffer(downloadedData)) // 跳过首行 _, err = csvData.Read() if err != nil { if err != io.EOF { log.Printf("读取CSV首行失败: %v", err) return fmt.Errorf("读取CSV首行失败: %w", err) } } for { line, err := csvData.Read() if err != nil { if err == io.EOF { break } log.Printf("读取CSV行失败: %v", err) return fmt.Errorf("读取CSV行失败: %w", err) } // 检查列数,避免索引越界 if len(line) < 5 { log.Printf("CSV行数据不足5列: %v", line) continue } data := fmt.Sprintf(`{"jsontext":"{\"No\":\"%s\",\"desc\":\"%s\",\"UOM\":\"%s\",\"cost\":\"%s\",\"price\":\"%s\"}"}`, line[0], line[1], line[2], line[3], line[4]) fmt.Printf("%s\n", data) payload := bytes.NewBuffer([]byte(data)) if err := postToBC(ctx, payload); err != nil { log.Printf("发送数据到BC失败: %v", err) return fmt.Errorf("发送数据到BC失败: %w", err) } break } return nil } func get_access_token(ctx context.Context) (string, error) { client_Id := os.Getenv("client_id") client_Secret := os.Getenv("client_secret") tenantID := os.Getenv("tenantID") // 检查环境变量是否存在 if client_Id == "" || client_Secret == "" || tenantID == "" { return "", fmt.Errorf("缺少必要的环境变量") } cred, err := confidential.NewCredFromSecret(client_Secret) if err != nil { log.Printf("创建凭据失败: %v", err) return "", fmt.Errorf("创建凭据失败: %w", err) } confidentialClient, err := confidential.New("https://login.microsoftonline.com/"+tenantID, client_Id, cred) if err != nil { log.Printf("创建机密客户端失败: %v", err) return "", fmt.Errorf("创建机密客户端失败: %w", err) } scopes := []string{"https://api.businesscentral.dynamics.com/.default"} result, err := confidentialClient.AcquireTokenSilent(ctx, scopes) if err != nil { result, err = confidentialClient.AcquireTokenByCredential(ctx, scopes) if err != nil { log.Printf("获取Token失败: %v", err) return "", fmt.Errorf("获取Token失败: %w", err) } } return result.AccessToken, nil } func postToBC(ctx context.Context, payload io.Reader) error { tenantID := os.Getenv("tenantID") if tenantID == "" { return fmt.Errorf("tenantID环境变量缺失") } url := "https://api.businesscentral.dynamics.com/v2.0/" + tenantID + "/sandbox2/ODataV4/MyWebService_InsertItemFromJson?company=Xeetek" method := "POST" // 设置HTTP超时 client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequestWithContext(ctx, method, url, payload) if err != nil { log.Printf("创建HTTP请求失败: %v", err) return fmt.Errorf("创建HTTP请求失败: %w", err) } req.Header.Add("Content-Type", "application/json") token, err := get_access_token(ctx) if err != nil { return fmt.Errorf("获取访问Token失败: %w", err) } req.Header.Add("Authorization", "Bearer "+token) res, err := client.Do(req) if err != nil { log.Printf("发送HTTP请求失败: %v", err) return fmt.Errorf("发送HTTP请求失败: %w", err) } defer res.Body.Close() if res.StatusCode >= 400 { body, _ := io.ReadAll(res.Body) log.Printf("BC返回错误状态码: %d, 响应内容: %s", res.StatusCode, string(body)) return fmt.Errorf("BC返回错误: %d, %s", res.StatusCode, string(body)) } body, err := io.ReadAll(res.Body) if err != nil { log.Printf("读取BC响应失败: %v", err) return fmt.Errorf("读取BC响应失败: %w", err) } fmt.Println(string(body)) return nil }
配置文件
local.settings.json
{ "IsEncrypted": false, "Values": { "FUNCTIONS_WORKER_RUNTIME": "custom", "AzureWebJobsStorage": "UseDevelopmentStorage=true" } }
host.json
{ "version": "2.0", "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[4.*, 5.0.0)" }, "customHandler": { "enableForwardingHttpRequest": true, "description": { "defaultExecutablePath": "Muaaz.exe", "workingDirectory": "", "arguments": [] } } }
function.json
{ "bindings": [ { "authLevel": "Anonymous", "type": "httpTrigger", "direction": "in", "name": "req", "methods": [ "get", "post" ], "route": "Ziyad" }, { "type": "http", "direction": "out", "name": "res" } ] }
关键修复与排查步骤
- 日志排查:登录Azure门户,进入Function App的「监控>日志」,查看具体错误栈,定位500错误的根源。
- 环境变量配置:将
client_id、client_secret、tenantID添加到Function App的「配置>应用程序设置」中,移除对本地.env文件的依赖。 - 权限配置:
- 给Function App的系统分配身份赋予存储账户的「存储Blob数据读取者」权限。
- 确保服务主体(对应client_id)拥有Business Central API的访问权限。
- 代码逻辑优化:
- 替换
log.Fatalln为错误返回,避免进程直接终止。 - 使用请求上下文
ctx替代context.Background(),适配Azure Function的生命周期。 - 添加CSV列数检查,防止索引越界panic。
- 给HTTP客户端设置超时,避免网络请求挂起。
- 替换
- Custom Handler验证:确保host.json中的
defaultExecutablePath与部署后的可执行文件名一致。
内容的提问来源于stack exchange,提问作者MAPMD
相关产品推荐
相关产品推荐

