66
77package com .microsoft .durabletask .azurefunctions ;
88
9+ import java .io .UnsupportedEncodingException ;
10+ import java .net .URLDecoder ;
11+ import java .nio .charset .StandardCharsets ;
12+ import java .util .ArrayList ;
13+ import java .util .Collections ;
14+ import java .util .LinkedHashMap ;
15+ import java .util .List ;
16+ import java .util .Locale ;
17+ import java .util .Map ;
18+
919/**
1020 * The class that holds the webhook URLs to manage orchestration instances.
1121 */
@@ -19,6 +29,7 @@ public class HttpManagementPayload {
1929 private final String terminatePostUri ;
2030 private final String resumePostUri ;
2131 private final String suspendPostUri ;
32+ private final Map <String , String > requiredHeaders ;
2233
2334 /**
2435 * Creates a {@link HttpManagementPayload} to manage orchestration instances
@@ -31,15 +42,81 @@ public HttpManagementPayload(
3142 String instanceId ,
3243 String instanceStatusURL ,
3344 String requiredQueryStringParameters ) {
45+ this (instanceId , instanceStatusURL , requiredQueryStringParameters , Collections .emptyMap ());
46+ }
47+
48+ /**
49+ * Creates management URLs with authentication supplied separately from the query string.
50+ *
51+ * @param instanceId The ID of the orchestration instance
52+ * @param instanceStatusURL The base webhook URL of the orchestration instance
53+ * @param requiredQueryStringParameters The non-secret query parameters to include with requests
54+ * @param requiredHeaders The headers callers must send when using the management URLs
55+ */
56+ public HttpManagementPayload (
57+ String instanceId ,
58+ String instanceStatusURL ,
59+ String requiredQueryStringParameters ,
60+ Map <String , String > requiredHeaders ) {
61+ Map <String , String > headers = new LinkedHashMap <>();
62+ if (requiredHeaders != null ) {
63+ requiredHeaders .forEach ((name , value ) -> headers .put (name .toLowerCase (Locale .ROOT ), value ));
64+ }
65+
66+ List <String > parameters = new ArrayList <>();
67+ String query = requiredQueryStringParameters == null ? "" : requiredQueryStringParameters ;
68+ if (query .startsWith ("?" )) {
69+ query = query .substring (1 );
70+ }
71+
72+ // Older hosts include the system key in the binding query string.
73+ for (String parameter : query .split ("&" )) {
74+ if (parameter .isEmpty ()) {
75+ continue ;
76+ }
77+
78+ int separator = parameter .indexOf ('=' );
79+ String name = separator < 0 ? parameter : parameter .substring (0 , separator );
80+ if (decode (name ).equalsIgnoreCase ("code" )) {
81+ if (separator >= 0 ) {
82+ headers .putIfAbsent ("x-functions-key" , decode (parameter .substring (separator + 1 )));
83+ }
84+ } else {
85+ parameters .add (parameter );
86+ }
87+ }
88+
89+ String queryString = String .join ("&" , parameters );
90+ String querySuffix = queryString .isEmpty () ? "" : "?" + queryString ;
91+ String reasonSuffix = "?reason={text}" + (queryString .isEmpty () ? "" : "&" + queryString );
3492 this .id = instanceId ;
35- this .purgeHistoryDeleteUri = instanceStatusURL + "?" + requiredQueryStringParameters ;
36- this .restartPostUri = instanceStatusURL + "/restart?" + requiredQueryStringParameters ;
37- this .rewindPostUri = instanceStatusURL + "/rewind?reason={text}&" + requiredQueryStringParameters ;
38- this .sendEventPostUri = instanceStatusURL + "/raiseEvent/{eventName}?" + requiredQueryStringParameters ;
39- this .statusQueryGetUri = instanceStatusURL + "?" + requiredQueryStringParameters ;
40- this .terminatePostUri = instanceStatusURL + "/terminate?reason={text}&" + requiredQueryStringParameters ;
41- this .resumePostUri = instanceStatusURL + "/resume?reason={text}&" + requiredQueryStringParameters ;
42- this .suspendPostUri = instanceStatusURL + "/suspend?reason={text}&" + requiredQueryStringParameters ;
93+ this .requiredHeaders = Collections .unmodifiableMap (headers );
94+ this .purgeHistoryDeleteUri = instanceStatusURL + querySuffix ;
95+ this .restartPostUri = instanceStatusURL + "/restart" + querySuffix ;
96+ this .rewindPostUri = instanceStatusURL + "/rewind" + reasonSuffix ;
97+ this .sendEventPostUri = instanceStatusURL + "/raiseEvent/{eventName}" + querySuffix ;
98+ this .statusQueryGetUri = instanceStatusURL + querySuffix ;
99+ this .terminatePostUri = instanceStatusURL + "/terminate" + reasonSuffix ;
100+ this .resumePostUri = instanceStatusURL + "/resume" + reasonSuffix ;
101+ this .suspendPostUri = instanceStatusURL + "/suspend" + reasonSuffix ;
102+ }
103+
104+ /**
105+ * Gets the headers callers must send when using the management URLs.
106+ * Treat these values as secrets; do not log them or append them to URLs.
107+ *
108+ * @return An immutable map of required HTTP request headers.
109+ */
110+ public Map <String , String > getRequiredHeaders () {
111+ return this .requiredHeaders ;
112+ }
113+
114+ private static String decode (String value ) {
115+ try {
116+ return URLDecoder .decode (value , StandardCharsets .UTF_8 .name ());
117+ } catch (UnsupportedEncodingException ex ) {
118+ throw new IllegalStateException ("UTF-8 encoding is unavailable." , ex );
119+ }
43120 }
44121
45122 /**
0 commit comments