# httpClient PUT doesn't seem to be including the body

**URL:** <https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792>\
**Category:** Ignition\
**Tags:** scripting\
**Created:** [October 20, 2023, 8:55pm UTC](https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792 "2023-10-20T20:55:34Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![tctx](https://avatars.discourse-cdn.com/v4/letter/t/d78d45/32.png) [@tctx](https://forum.inductiveautomation.com/u/tctx)\
**Post date:** [October 20, 2023, 8:55pm UTC](https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792/1 "2023-10-20T20:55:34Z")

</div>

I'm trying to send a single observation of a collection of tags to a Web API endpoint. The Web API expects a PUT request with a JSON body that has a few string:float entries.

I'm attempting to use an `system.net.httpClient()` instance to do this. Here's my example Ignition script:

```python
import pprint
import json

test_tag = system.tag.browse("[default]KPIs/ModelTags")[5]
body = mycode.build_put_request_body_dict(test_tag)
print "Content to include in body:"
print json.dumps(body, indent=4)

url = mycode.api_url
client = system.net.httpClient()
response = client.put(url, headers={"Content-Type":"application/json"}, data=body)
print "\nResponse from API:"
print response.statusCode
print pprint.pprint(response.getJson())

```

And the subsequent console output:

```auto
Content to include in body:
{
    "uf_seawater_inlet_hdr_temp": 152.267133838642, 
    "reject_pressure_stg_2_psi": 167.244077783062, 
    "avg_flux_stg_1_gfd": 0.1068650075460458, 
    "permeate_tds_mgL": 161.253300205294, 
    "feed_temp_deg_F": 129.80171792201202, 
    "reject_pressure_stg_1_psi": 152.267133838642, 
    "uf_diff_pressure": 152.267133838642, 
    "perm_pressure_psi": 152.267133838642, 
    "feed_pressure_psi": 183.71871612192402, 
    "uf_buffer_outlet_flow": 153.76482823308402
}

Response from API:
422
{'detail': [{'input': None,
             'loc': [u'body'],
             'msg': u'Field required',
             'type': u'missing',
             'url': u'https://errors.pydantic.dev/2.4/v/missing'}]}

```

You can see that the Pydantic schema on the Web API is complaining that the body is missing. However, if I manually send a `curl` command from the Ignition machine with the same PUT and same body to the web API, I get the expected response.

This seems to only apply to trying to have Ignition send a PUT (or POST) with a body...all the GET requests to the API work fine.

Further info, here is the trace from the web API server when it gets the PUT from a `curl` command, note that it received 415 bytes in the body:

```auto
TRACE: 172.20.70.56:58233 - HTTP connection made
TRACE: 172.20.70.56:58233 - ASGI [9] Started scope={'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('172.20.70.55', 80), 'client': ('172.20.70.56', 58233), 'scheme': 'http', 'method': 'PUT', 'root_path': '', 'path': '/model/fouling-factor-first-pass', 'raw_path': b'/model/fouling-factor-first-pass', 'query_string': b'', 'headers': '<...>', 'state': {}}
TRACE: 172.20.70.56:58233 - ASGI [9] Receive {'type': 'http.request', 'body': '<415 bytes>', 'more_body': False}
TRACE: 172.20.70.56:58233 - ASGI [9] Send {'type': 'http.response.start', 'status': 200, 'headers': '<...>'}
INFO: 172.20.70.56:58233 - "PUT /model/fouling-factor-first-pass HTTP/1.1" 200 OK
TRACE: 172.20.70.56:58233 - ASGI [9] Send {'type': 'http.response.body', 'body': '<69 bytes>'}
TRACE: 172.20.70.56:58233 - ASGI [9] Completed
TRACE: 172.20.70.56:58233 - HTTP connection lost

```

Compared to the trace from a PUT request from the Ignition script, note that it receives **0 bytes** in the body therefore it sends back 422 since it didn't match the schema:

```auto
TRACE: 172.20.70.56:55738 - HTTP connection made
TRACE: 172.20.70.56:55738 - ASGI [2] Started scope={'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('172.20.70.55', 80), 'client': ('172.20.70.56', 55738), 'scheme': 'http', 'root_path': '', 'headers': '<...>', 'state': {}, 'method': 'PUT', 'path': '/model/fouling-factor-first-pass', 'raw_path': b'/model/fouling-factor-first-pass', 'query_string': b''}
TRACE: 172.20.70.56:55738 - ASGI [2] Receive {'type': 'http.request', 'body': '<0 bytes>', 'more_body': False}
TRACE: 172.20.70.56:55738 - ASGI [2] Send {'type': 'http.response.start', 'status': 422, 'headers': '<...>'}
INFO: 172.20.70.56:55738 - "PUT /model/fouling-factor-first-pass HTTP/1.1" 422 Unprocessable Entity
TRACE: 172.20.70.56:55738 - ASGI [2] Send {'type': 'http.response.body', 'body': '<132 bytes>'}
TRACE: 172.20.70.56:55738 - ASGI [2] Completed
TRACE: 172.20.70.56:55738 - HTTP connection lost

```

So I'm at a loss at this point. Appreciate any ideas.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [October 20, 2023, 10:06pm UTC](https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792/2 "2023-10-20T22:06:05Z")

</div>

> [@tctx](#):
>
> `body = mycode.build_put_request_body_dict(test_tag)`

What type of object are you actually building here?

---

<div class="post-metadata">

**Author:** ![tctx](https://avatars.discourse-cdn.com/v4/letter/t/d78d45/32.png) [@tctx](https://forum.inductiveautomation.com/u/tctx)\
**Post date:** [October 20, 2023, 11:06pm UTC](https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792/3 "2023-10-20T23:06:48Z")

</div>

Just a plain old `dict`, with `str` keys and `float` values.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [October 20, 2023, 11:18pm UTC](https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792/4 "2023-10-20T23:18:28Z")

</div>

Hmm.

Are you sure that API response means the entire body is missing and not just one field it expects?

Might print out `type(body)` just to make sure. Not sure why else it wouldn't get sent.

---

<div class="post-metadata">

**Author:** ![tctx](https://avatars.discourse-cdn.com/v4/letter/t/d78d45/32.png) [@tctx](https://forum.inductiveautomation.com/u/tctx)\
**Post date:** [October 20, 2023, 11:35pm UTC](https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792/5 "2023-10-20T23:35:34Z")

</div>

> [@Kevin.Herron](#):
>
> Are you sure that API response means the entire body is missing and not just one field it expects?

Yep, for example if I intentionally leave out one of the pairs in a PUT from another source, the response will tell me specifically what I'm missing. Note the "input" field giving me back my (incomplete) body, compared to input: None from the Ignition request.

```auto
{
  "detail": [
    {
      "type": "missing",
      "loc": [
        "body",
        "uf_buffer_outlet_flow"
      ],
      "msg": "Field required",
      "input": {
        "uf_seawater_inlet_hdr_temp": 152.092210957528,
        "reject_pressure_stg_2_psi": 167.05183580480804,
        "avg_flux_stg_1_gfd": 0.1067422188882482,
        "permeate_tds_mgL": 161.06798586589602,
        "feed_temp_deg_F": 129.65277368660801,
        "reject_pressure_stg_1_psi": 152.092210957528,
        "uf_diff_pressure": 152.092210957528,
        "perm_pressure_psi": 152.092210957528,
        "feed_pressure_psi": 183.50742313681604
      },
      "url": "https://errors.pydantic.dev/2.4/v/missing"
    }
  ]
}

```

> [@Kevin.Herron](#):
>
> Might print out `type(body)` just to make sure. Not sure why else it wouldn't get sent.

```auto
import pprint
import json

test_tag = system.tag.browse("[default]KPIs/ModelTags")[5]
body = mycode.build_put_request_body_dict(test_tag)
print type(body)
print "Content to include in body:"
print json.dumps(body, indent=4)

url = mycode.api_url
client = system.net.httpClient()
response = client.put(url, headers={"Content-Type":"application/json"}, data=body)
print "\nResponse from API:"
print response.statusCode
print pprint.pprint(response.getJson())

```

gives

```auto
<type 'dict'>
Content to include in body:
{
    "uf_seawater_inlet_hdr_temp": 159.07369183022502, 
    "reject_pressure_stg_2_psi": 174.72455240747502, 
    "avg_flux_stg_1_gfd": 0.1116429309068178, 
    "permeate_tds_mgL": 168.464208176575, 
    "feed_temp_deg_F": 135.59740096435002, 
    "reject_pressure_stg_1_psi": 159.07369183022502, 
    "uf_diff_pressure": 159.07369183022502, 
    "perm_pressure_psi": 159.07369183022502, 
    "feed_pressure_psi": 191.94049904245003, 
    "uf_buffer_outlet_flow": 160.63877788795003
}

Response from API:
422
{'detail': [{'input': None,
             'loc': [u'body'],
             'msg': u'Field required',
             'type': u'missing',
             'url': u'https://errors.pydantic.dev/2.4/v/missing'}]}

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [October 20, 2023, 11:41pm UTC](https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792/6 "2023-10-20T23:41:53Z")

</div>

Consider encoding your json to bytes yourself and supplying the byte array instead.

{ Don't use jython's json library. Use `system.util.jsonEncode()`. }

For the bytes, I recommend sticking the following in a project library:

```auto
from java.lang import String
from java.nio.charset import Charset, StandardCharsets

_getBytesCS = String.getMethod('getBytes', Charset)

def encodeString(s, cs=StandardCharsets.UTF_8):
	return _getBytesCS.invoke(s, cs)

```

Then you can do:

```auto
bytes = someLibrary.encodeString(system.util.jsonEncode(body))

```

---

<div class="post-metadata">

**Author:** ![tctx](https://avatars.discourse-cdn.com/v4/letter/t/d78d45/32.png) [@tctx](https://forum.inductiveautomation.com/u/tctx)\
**Post date:** [October 21, 2023, 12:20am UTC](https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792/7 "2023-10-21T00:20:12Z")

</div>

Same result, unfortunately.

```auto
import pprint
import json

test_tag = system.tag.browse("[default]KPIs/ModelTags")[5]
body = mycode.build_put_request_body_dict(test_tag)
print type(body)
print "Content to include in body:"
print json.dumps(body, indent=4)

bytes = utils.encodeString(system.util.jsonEncode(body))
print "Byte array version:"
print bytes

url = mycode.api_url
client = system.net.httpClient()
response = client.put(url, headers={"Content-Type":"application/json"}, data=bytes)
print "\nResponse from API:"
print response.statusCode
print pprint.pprint(response.getJson())

```

```auto
<type 'dict'>
Content to include in body:
{
    "uf_seawater_inlet_hdr_temp": 159.07369183022502, 
    "reject_pressure_stg_2_psi": 174.72455240747502, 
    "avg_flux_stg_1_gfd": 0.1116429309068178, 
    "permeate_tds_mgL": 168.464208176575, 
    "feed_temp_deg_F": 135.59740096435002, 
    "reject_pressure_stg_1_psi": 159.07369183022502, 
    "uf_diff_pressure": 159.07369183022502, 
    "perm_pressure_psi": 159.07369183022502, 
    "feed_pressure_psi": 191.94049904245003, 
    "uf_buffer_outlet_flow": 160.63877788795003
}
Byte array version:
array('b', [123, 34, 117, 102, 95, 115, 101, 97, 119, 97, 116, 101, 114, 95, 105, 110, 108, 101, 116, 95, 104, 100, 114, 95, 116, 101, 109, 112, 34, 58, 49, 53, 57, 46, 48, 55, 51, 54, 57, 49, 56, 51, 48, 50, 50, 53, 48, 50, 44, 34, 114, 101, 106, 101, 99, 116, 95, 112, 114, 101, 115, 115, 117, 114, 101, 95, 115, 116, 103, 95, 49, 95, 112, 115, 105, 34, 58, 49, 53, 57, 46, 48, 55, 51, 54, 57, 49, 56, 51, 48, 50, 50, 53, 48, 50, 44, 34, 117, 102, 95, 100, 105, 102, 102, 95, 112, 114, 101, 115, 115, 117, 114, 101, 34, 58, 49, 53, 57, 46, 48, 55, 51, 54, 57, 49, 56, 51, 48, 50, 50, 53, 48, 50, 44, 34, 114, 101, 106, 101, 99, 116, 95, 112, 114, 101, 115, 115, 117, 114, 101, 95, 115, 116, 103, 95, 50, 95, 112, 115, 105, 34, 58, 49, 55, 52, 46, 55, 50, 52, 53, 53, 50, 52, 48, 55, 52, 55, 53, 48, 50, 44, 34, 112, 101, 114, 109, 95, 112, 114, 101, 115, 115, 117, 114, 101, 95, 112, 115, 105, 34, 58, 49, 53, 57, 46, 48, 55, 51, 54, 57, 49, 56, 51, 48, 50, 50, 53, 48, 50, 44, 34, 102, 101, 101, 100, 95, 112, 114, 101, 115, 115, 117, 114, 101, 95, 112, 115, 105, 34, 58, 49, 57, 49, 46, 57, 52, 48, 52, 57, 57, 48, 52, 50, 52, 53, 48, 48, 51, 44, 34, 97, 118, 103, 95, 102, 108, 117, 120, 95, 115, 116, 103, 95, 49, 95, 103, 102, 100, 34, 58, 48, 46, 49, 49, 49, 54, 52, 50, 57, 51, 48, 57, 48, 54, 56, 49, 55, 56, 44, 34, 117, 102, 95, 98, 117, 102, 102, 101, 114, 95, 111, 117, 116, 108, 101, 116, 95, 102, 108, 111, 119, 34, 58, 49, 54, 48, 46, 54, 51, 56, 55, 55, 55, 56, 56, 55, 57, 53, 48, 48, 51, 44, 34, 112, 101, 114, 109, 101, 97, 116, 101, 95, 116, 100, 115, 95, 109, 103, 76, 34, 58, 49, 54, 56, 46, 52, 54, 52, 50, 48, 56, 49, 55, 54, 53, 55, 53, 44, 34, 102, 101, 101, 100, 95, 116, 101, 109, 112, 95, 100, 101, 103, 95, 70, 34, 58, 49, 51, 53, 46, 53, 57, 55, 52, 48, 48, 57, 54, 52, 51, 53, 48, 48, 50, 125])

Response from API:
422
{'detail': [{'input': None,
             'loc': [u'body'],
             'msg': u'Field required',
             'type': u'missing',
             'url': u'https://errors.pydantic.dev/2.4/v/missing'}]}

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [October 21, 2023, 12:23am UTC](https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792/8 "2023-10-21T00:23:01Z")

</div>

Hmmm. I'd be looking at wireshark next. Comparing the traffic difference between your working stand-alone PUT and the one in Ignition.

---

<div class="post-metadata">

**Author:** ![tctx](https://avatars.discourse-cdn.com/v4/letter/t/d78d45/32.png) [@tctx](https://forum.inductiveautomation.com/u/tctx)\
**Post date:** [October 21, 2023, 12:55am UTC](https://forum.inductiveautomation.com/t/httpclient-put-doesnt-seem-to-be-including-the-body/80792/9 "2023-10-21T00:55:58Z")

</div>

Well, the difference was the working PUT was specifying `connection: keep-alive` whereas Ignition was specifying `connection: upgrade` to h2c

A quick check server side and indeed Uvicorn doesn't support HTTP/2, so changing to `system.net.httpClient(version="HTTP_1_1")` has fixed the issue.

Thank you both!
