Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-68154

Custom headers with special characters in their names

      According to the documentation of the plugin, you can define custom headers for requests with a syntax like this one:

      def response = httpRequest customHeaders: [[name: 'foo', value: 'bar']]
      

      The problem arises when you try to use custom headers that use names with non-standard characters. I can't see the way to do something like this:

      def response = httpRequest customHeaders: [["my-custom-header-with-hyphen-separator": "${varName}"]]

      I know I can do it the hard way:

      def headers = new ArrayList<jenkins.plugins.http_request.util.HttpRequestNameValuePair>()
      
      def headerName1 = 'my-custom-header-with-hyphen-separator'
      def varName1 = 'abc'
      
      headers.add(new jenkins.plugins.http_request.util.HttpRequestNameValuePair("${headerName1}","${varName1}"))
      

      However I don't think that's the preferred method as it involves granting certain permissions to scripts.

      Under normal circumstances, I would've expected here to use this kind of syntax:

      def headers = [ "${headerName1}":"${varName1}","${headerName2}": "${varName2}" ]
      

      Pretty much a json object with one or more key/value pairs.

      So, is this possible? is there any alternative syntax that can be used right now to avoid working directly with Java classes?

       

          [JENKINS-68154] Custom headers with special characters in their names

          Alfredo Quesada created issue -

          I have an implementation ready for this improvement but awaiting feedback from a JEP-200 ticket I have opened because there might be security concerns:
          https://issues.jenkins.io/browse/JENKINS-71075

          Christos Fitsialis added a comment - I have an implementation ready for this improvement but awaiting feedback from a JEP-200 ticket I have opened because there might be security concerns: https://issues.jenkins.io/browse/JENKINS-71075

          Pratik added a comment - - edited

          markewaite janario aquesada Can I work on this issue ?

          Pratik added a comment - - edited markewaite janario aquesada Can I work on this issue ?

          Mark Waite added a comment -

          pratik_0112 you are welcome to work on it. Thanks for noting your interest,

          Mark Waite added a comment - pratik_0112 you are welcome to work on it. Thanks for noting your interest,
          Pratik made changes -
          Assignee Original: Janario Oliveira [ janario ] New: Pratik [ pratik_0112 ]
          Pratik made changes -
          Comment [ [~markewaite] Sir, 

          I'm encountering an issue with the round-trip tests in the HTTP Request plugin. When I run the tests, they fail with a 500 server error during the configSubmit phase (for example, for URLs like {{{}http://localhost:36357/jenkins/job/test2/configSubmit{}}}). However, when I tried it on my local Jenkins instance, the pipeline runs successfully without any errors.

          Pipeline 1 : 

          pipeline {
              agent any
              stages {
                  stage('Test HTTP Request with Custom Headers and Variables') {
                      steps {
                          script {
                              echo "Starting HTTP Request with custom headers using non-standard header names"

                              // Define a variable to use in the custom header value
                              def varName = "test-value"

                              // Make an HTTP GET request with custom headers
                              def response = httpRequest(
                                  url: 'https://httpbin.org/headers&#39;, // Echoes back the headers received
                                  customHeaders: [["my-custom-header-with-hyphen-separator": "${varName}"]]
                              )

                              // Log the response content
                              echo "HTTP Response Content: ${response.content}"
                          }
                      }
                  }
              }
          } ]
          Pratik made changes -
          Comment [ Hi [~markewaite], [~aquesada] & [~chr1st0s]  Sir, 

          I created a simple pipeline to test the issue -
          {quote}pipeline {
              agent any
              stages {
                  stage('Test HTTP Request with Custom Headers') {
                      steps {
                          script {
                              echo "Starting HTTP Request with custom headers"

                              def response = httpRequest(
                                  url: 'https://httpbin.org/headers&#39;,
                                  customHeaders: [[name: 'foo', value: 'bar']]
                              )
                              
                              echo "HTTP Response Content: ${response.content}"
                          }
                      }
                  }
              }
          }
          {quote}
           
           * {{I build it through mvn hpi:run but it gives console log for each build as :}}

           
          {quote}{{Started by user unknown or anonymous}}
          {{[Pipeline] Start of Pipeline}}
          {{[Pipeline] End of Pipeline}}
          {{Finished: SUCCESS}}
          {quote} * {{When I tried with my local jenkins controller with sudo systemctl start jenkins it works as expected and print the Response for all builds :}}

           
           * {{[Pipeline] echo}}
          {{HTTP Response Content: {}}
          {{  "headers": {}}
          {{    "Accept-Encoding": "gzip,deflate", }}
          {{    "Foo": "bar", }}
          {{    "Host": "httpbin.org", }}
          {{    "User-Agent": "Apache-HttpClient/4.5.14 (Java/17.0.13)", }}
          {{    "X-Amzn-Trace-Id": "Root=1-6780ee9b-3fbacca92b24045340f07192"}} \{\{  }
          }}
          {{}}}

           * {{My doubt is, what is issue with my "mvn hpi:run" jenkins controller, Am I doing in wrong way?}}
           *  A glance of my terminal output, after running mvn hpi:run :

           
          {quote}[INFO] Configuring Jetty for project: HTTP Request Plugin
          [INFO] Try setting -DwildcardDNS=nip.io in a profile
          [INFO] ===========> Browse to: [http://localhost:8080/jenkins/]
          [INFO] /home/pratik@0112/GSoC/http-request-plugin/target/jetty/webapp does not yet exist, will receive /home/pratik@0112/.m2/repository/org/jenkins-ci/main/jenkins-war/2.479.1/jenkins-war-2.479.1.war
          [INFO] Context path = /jenkins
          [INFO] Tmp directory = /home/pratik@0112/GSoC/http-request-plugin/target/jetty
          [INFO] War = /home/pratik@0112/.m2/repository/org/jenkins-ci/main/jenkins-war/2.479.1/jenkins-war-2.479.1.war
          [INFO] jetty-12.0.16; built: 2024-12-09T21:02:54.535Z; git: c3f88bafb4e393f23204dc14dc57b042e84debc7; jvm 17.0.13+11-Ubuntu-2ubuntu124.04
          [INFO] NO JSP Support for /jenkins, did not find org.eclipse.jetty.ee9.jsp.JettyJspServlet
          [INFO] Session workerName=node0
          2025-01-10 09:51:20.782+0000 [id=1]     INFO    hudson.WebAppMain#contextInitialized: Jenkins home directory: /home/pratik@0112/GSoC/http-request-plugin/work found at: SystemProperties.getProperty("JENKINS_HOME")
          [INFO] Started oeje9n.ContextHandler$CoreContextHandler@239fd1ad{Jenkins v2.479.1,/jenkins,b=[file:///home/pratik@0112/GSoC/http-request-plugin/target/jetty/webapp/,a=AVAILABLE,h=oeje9n.ContextHandler$CoreContextHandler$CoreToNestedHandler@51e42a85]{STARTED}}
          [INFO] Started ServerConnector@3bc67aee\{HTTP/1.1, (http/1.1)}
          Unknown macro: \{localhost}
          [INFO] Started oejs.Server@68a4179f\{STARTING}[12.0.16,sto=0] @16552ms

          Hit <enter> to redeploy:

          2025-01-10 09:51:21.591+0000 [id=36]    INFO    jenkins.InitReactorRunner$1#onAttained: Started initialization
          2025-01-10 09:51:21.687+0000 [id=44]    INFO    hudson.ClassicPluginStrategy#createPluginWrapper: Plugin http_request.hpl is disabled
          2025-01-10 09:51:21.786+0000 [id=47]    INFO    jenkins.InitReactorRunner$1#onAttained: Listed all plugins
          2025-01-10 09:51:24.095+0000 [id=47]    INFO    jenkins.InitReactorRunner$1#onAttained: Prepared all plugins
          2025-01-10 09:51:24.122+0000 [id=35]    INFO    jenkins.InitReactorRunner$1#onAttained: Started all plugins
          2025-01-10 09:51:24.126+0000 [id=46]    INFO    jenkins.InitReactorRunner$1#onAttained: Augmented all extensions
          2025-01-10 09:51:24.896+0000 [id=48]    INFO    jenkins.InitReactorRunner$1#onAttained: System config loaded
          2025-01-10 09:51:24.897+0000 [id=37]    INFO    jenkins.InitReactorRunner$1#onAttained: System config adapted
          2025-01-10 09:51:24.964+0000 [id=39]    INFO    jenkins.InitReactorRunner$1#onAttained: Loaded all jobs
          2025-01-10 09:51:24.999+0000 [id=39]    INFO    jenkins.InitReactorRunner$1#onAttained: Configuration for all jobs updated
          2025-01-10 09:51:25.031+0000 [id=39]    INFO    jenkins.model.Jenkins#setInstallState: Install state transitioning from: null to: DEVELOPMENT
          2025-01-10 09:51:25.350+0000 [id=38]    INFO    jenkins.InitReactorRunner$1#onAttained: Completed initialization
          2025-01-10 09:51:25.484+0000 [id=31]    INFO    hudson.lifecycle.Lifecycle#onReady: Jenkins is fully up and running

           
          {quote} ]
          Pratik made changes -
          Comment [ [~markewaite] Sir,

          I'm encountering an issue with the round-trip tests in the HTTP Request plugin. When I run the tests, they fail with a 500 server error during the configSubmit phase (for example, for URLs like {{{}[http://localhost:36357/jenkins/job/test2/configSubmit]{}}}). However, when I tried it on my local Jenkins instance, the pipeline runs successfully without any errors.

          Additionally, I wanted to ask about adding the following pipelines and updating the code:
           - Pipelines :

          {{                   }}
          {{                   def response = httpRequest(}}
          {{                        url: 'https://httpbin.org/headers&#39;,}}
          {{                        customHeaders: [[name: 'foo', value: 'bar']]}}
          {{                    )}}

           

          {{                    def headerName1 = 'header'}}
          {{                    def varName1 = 'abc'}}
          {{                    def headerName2 = 'another-header'}}
          {{                    def varName2 = 'xyz'}}
          {{                    def headers = [}}
          {{                        "${headerName1}": "${varName1}",}}
          {{                        "${headerName2}": "${varName2}"}}
          {{                    ]}}
          {{                    def response = httpRequest(url:'https://httpbin.org/headers&#39;,&#xA0; customHeaders: headers)}}



          - Updated Code for {{HttpRequestStep.java}} and {{HttpRequest.java :}}

           
          import java.util.Map;
          import java.util.stream.Collectors;
           
           
          @DataBoundSetter
          public void setCustomHeaders(Object customHeaders) {
          if (customHeaders instanceof List) {
          // Check if the list contains Map entries
          List<?> list = (List<?>) customHeaders;
          if (!list.isEmpty() && list.get(0) instanceof Map) {
          this.customHeaders = list.stream()
          .map(entry ->

           

          { Map<String, String> map = (Map<String, String>) entry; return new HttpRequestNameValuePair(map.get("name"), map.get("value")); }

          )
          .collect(Collectors.toList());
          } else

          { this.customHeaders = (List<HttpRequestNameValuePair>) customHeaders; }

          } else if (customHeaders instanceof Map)

          { this.customHeaders = ((Map<String, String>) customHeaders) .entrySet() .stream() .map(entry -> new HttpRequestNameValuePair(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); }

          else

          { throw new IllegalArgumentException("Unsupported type for customHeaders: " + customHeaders.getClass()); }

          }
           

          - Test for customHeaders in HttpRequestRoundTripTest.java & HttpRequestStepRoundTripTest.java

            @Test
              public void configRoundtripGroup3() throws Exception {
                  configRoundTrip(before);

                  List<HttpRequestNameValuePair> params = new ArrayList<>();
                  params.add(new HttpRequestNameValuePair("param1","value1"));
                  params.add(new HttpRequestNameValuePair("param2","value2"));

                  RequestAction action = new RequestAction(new URL("http://www.domain.com/"),HttpMode.GET,null,params);
                  List<RequestAction> actions = new ArrayList<>();
                  actions.add(action);

                  FormAuthentication formAuth = new FormAuthentication("keyname",actions);
                  List<FormAuthentication> formAuthList = new ArrayList<>();
                  formAuthList.add(formAuth);

                  HttpRequestGlobalConfig.get().setFormAuthentications(formAuthList);
                  configRoundTrip(before);

                  List<HttpRequestNameValuePair> customHeaders = new ArrayList<>();
                  customHeaders.add(new HttpRequestNameValuePair("param1","value1"));
                  before.setCustomHeaders(customHeaders);
                  configRoundTrip(before);
              }

           private void configRoundTrip(HttpRequest before) throws Exception {
                  HttpRequest after = j.configRoundtrip(before);
                  j.assertEqualBeans(before, after, "httpMode,passBuildParameters");
                  j.assertEqualBeans(before, after, "url");
                  j.assertEqualBeans(before, after, "validResponseCodes,validResponseContent");
                  j.assertEqualBeans(before, after, "acceptType,contentType");
                  j.assertEqualBeans(before, after, "uploadFile,multipartName");
                  j.assertEqualBeans(before, after, "outputFile,timeout");
                  j.assertEqualBeans(before, after, "consoleLogResponseBody");
                  j.assertEqualBeans(before, after, "authentication");

                  // Custom header check
                  assertEquals(before.getCustomHeaders().size(),after.getCustomHeaders().size());
                  for (int idx = 0; idx < before.getCustomHeaders().size(); idx++) {
                    HttpRequestNameValuePair bnvp = before.getCustomHeaders().get(idx);
                    HttpRequestNameValuePair anvp = after.getCustomHeaders().get(idx);
                    assertEquals(bnvp.getName(),anvp.getName());
                    assertEquals(bnvp.getValue(),anvp.getValue());
                  }

                  // Form authentication check
                  List<FormAuthentication> beforeFas = HttpRequestGlobalConfig.get().getFormAuthentications();
                  List<FormAuthentication> afterFas  = HttpRequestGlobalConfig.get().getFormAuthentications();
                  assertEquals(beforeFas.size(), afterFas.size());
                  for (int idx = 0; idx < beforeFas.size(); idx++) {
                      FormAuthentication beforeFa = beforeFas.get(idx);
                      FormAuthentication afterFa  = afterFas.get(idx);
                      assertEquals(beforeFa.getKeyName(), afterFa.getKeyName());
                      List<RequestAction> beforeActions = beforeFa.getActions();
                      List<RequestAction> afterActions  = afterFa.getActions();
                      assertEquals(beforeActions.size(), afterActions.size());
                      for (int jdx = 0; jdx < beforeActions.size(); jdx ++) {
                          RequestAction beforeAction = beforeActions.get(jdx);
                          RequestAction afterAction  = afterActions.get(jdx);
                          assertEquals(beforeAction.getUrl(), afterAction.getUrl());
                          assertEquals(beforeAction.getMode(), afterAction.getMode());
                          List<HttpRequestNameValuePair> beforeParams = beforeAction.getParams();
                          List<HttpRequestNameValuePair> afterParams  = afterAction.getParams();
                          assertEquals(beforeParams.size(), afterParams.size());
                          for (int kdx = 0; kdx < beforeParams.size(); kdx++) {
                              HttpRequestNameValuePair beforeNvp = beforeParams.get(kdx);
                              HttpRequestNameValuePair afterNvp  = afterParams.get(kdx);
                              assertEquals(beforeNvp.getName(), afterNvp.getName());
                              assertEquals(beforeNvp.getValue(), afterNvp.getValue());
                          }
                      }
                  }
              }
          }
          {quote}{{I wanted to know how I can integrate these updates properly. Do you think the issue could be related to the testing environment, or is there something else I should investigate further?"}}
          {quote} ]
          Pratik made changes -
          Comment [ [~markewaite] 
          Actually my one PR got merged in this plugin of "updating parent pom" but that time I was new, don't know about dependabot (he does it). But now, i am more familiar with jenkins platform. I will start on this right now, if I have come across any error further while solving, I will let you know. Please guide me that time !!! ]
          Pratik made changes -
          Comment [ [~markewaite], [~aquesada] [~chr1st0s]
          After trying hard on this issue, I am able to execute pipeline with both customHeaders format mentioned in this issue on my local jenkins instance but getting below error while testing  locally for 3 test cases -



          ```

          [ERROR] Tests run: 5, Failures: 0, Errors: 3, Skipped: 0, Time elapsed: 276.0 s <<< FAILURE! -- in jenkins.plugins.http_request.HttpRequestRoundTripTest
          [ERROR] jenkins.plugins.http_request.HttpRequestRoundTripTest.configRoundtripGroup3 -- Time elapsed: 50.63 s <<< ERROR!
          org.htmlunit.FailingHttpStatusCodeException: 500 Server Error for http://localhost:45745/jenkins/job/test2/configSubmit
                  at org.htmlunit.WebClient.throwFailingHttpStatusCodeExceptionIfNecessary(WebClient.java:749)
                  at org.htmlunit.WebClient.loadDownloadedResponses(WebClient.java:2720)
                  at org.htmlunit.html.HtmlFormUtil.submit(HtmlFormUtil.java:79)
                  at org.htmlunit.html.HtmlFormUtil.submit(HtmlFormUtil.java:53)
                  at org.jvnet.hudson.test.JenkinsRule.submit(JenkinsRule.java:1790)
                  at org.jvnet.hudson.test.JenkinsRule.configRoundtrip(JenkinsRule.java:1447)
                  at org.jvnet.hudson.test.JenkinsRule.configRoundtrip(JenkinsRule.java:1457)
                  at jenkins.plugins.http_request.HttpRequestRoundTripTest.configRoundTrip(HttpRequestRoundTripTest.java:119)
                  at jenkins.plugins.http_request.HttpRequestRoundTripTest.configRoundtripGroup3(HttpRequestRoundTripTest.java:87)
                  at java.base/java.lang.reflect.Method.invoke(Method.java:569)
                  at org.jvnet.hudson.test.JenkinsRule$1.evaluate(JenkinsRule.java:658)
                  at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
                  at java.base/java.lang.Thread.run(Thread.java:840)
          ``` ]

            pratik_0112 Pratik
            aquesada Alfredo Quesada
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: