upload: Cache the boto client to improve performance.
upload: Cache the boto client to improve performance.

Fixes #18915

This was very slow, causing performance issues. After investigating,
generate_presigned_url is the cheap part of this, but the
session.client() call is expensive - so that's what we should cache.

Before the change:
```
In [4]: t = time.time()
   ...: for i in range(250):
   ...:     x = u.get_public_upload_url("foo")
   ...: print(time.time()-t)
6.408717393875122
```

After:
```
In [4]: t = time.time()
   ...: for i in range(250):
   ...:     x = u.get_public_upload_url("foo")
   ...: print(time.time()-t)
0.48990607261657715
```

This is not good enough to avoid doing something ugly like replacing
generate_presigned_url with some manual URL manipulation, but it's a
helpful structure that we may find useful with further refactoring.

eeshangarg/zulip,andersk/zulip,hackerkid/zulip,punchagan/zulip,eeshangarg/zulip,punchagan/zulip,hackerkid/zulip,rht/zulip,zulip/zulip,zulip/zulip,punchagan/zulip,andersk/zulip,eeshangarg/zulip,zulip/zulip,andersk/zulip,zulip/zulip,kou/zulip,rht/zulip,kou/zulip,punchagan/zulip,hackerkid/zulip,eeshangarg/zulip,zulip/zulip,kou/zulip,kou/zulip,rht/zulip,hackerkid/zulip,hackerkid/zulip,zulip/zulip,rht/zulip,punchagan/zulip,rht/zulip,kou/zulip,andersk/zulip,rht/zulip,punchagan/zulip,andersk/zulip,punchagan/zulip,eeshangarg/zulip,kou/zulip,kou/zulip,rht/zulip,hackerkid/zulip,andersk/zulip,zulip/zulip,hackerkid/zulip,eeshangarg/zulip,eeshangarg/zulip,andersk/zulip