Added shared SSLContext to speed up web requests

This commit is contained in:
Eric Froemling 2022-07-09 16:26:04 -07:00
parent 84c9f1f787
commit 6af6c29c75
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
13 changed files with 663 additions and 632 deletions

File diff suppressed because it is too large Load Diff

View File

@ -176,6 +176,7 @@
<w>bacfg</w>
<w>backgrounded</w>
<w>backgrounding</w>
<w>backporting</w>
<w>backwin</w>
<w>bacloud</w>
<w>bacloudcmd</w>
@ -2468,6 +2469,7 @@
<w>tdels</w>
<w>tdiff</w>
<w>tdlds</w>
<w>tdstpath</w>
<w>tdval</w>
<w>teambasesession</w>
<w>teamdata</w>
@ -2600,6 +2602,7 @@
<w>tscl</w>
<w>tself</w>
<w>tspc</w>
<w>tsrcpath</w>
<w>tstr</w>
<w>tunmd</w>
<w>tupleval</w>

View File

@ -1,4 +1,4 @@
### 1.7.4 (20644, 2022-07-08)
### 1.7.4 (20645, 2022-07-09)
- Fixed the trophies list showing an incorrect total (Thanks itsre3!)
- ba.app.meta.metascan is now ba.app.meta.scanresults
- Cleaned up co-op ui code a bit
@ -7,6 +7,7 @@
- Android user scripts dir is now called 'mods' instead of 'BombSquad'. The name 'BombSquad' made sense when it was located in a truly shared area of storage but now that it is in the app-specific area (something like Android/data/net.froemling.bombsquad/files) it makes sense to just use 'mods' like other platforms.
- Updated the 'Show Mods Folder' to properly show the path to the mods folder. Before it would unhelpfully show something like `<External Storage>/mods` but now it should be something more useful like `Android/data/net.froemling.bombsquad/files/mods`.
- Updated the Modding Guide button in advanced settings to point to the new ballistica wiki stuff instead of the old out-of-date 1.4 modding docs.
- Added ba.app.net.sslcontext which is a shared SSLContext we can recycle for our https requests. It turns out it can take upwards of 1 second on older Android devices to create a default SSLContext, so this can provide a nice speedup compared to the default behavior of creating a new default one for each request.
### 1.7.3 (20634, 2022-07-06)
- Fixed an issue with King of the Hill flag regions not working when players entered them (Thanks itsre3!)

View File

@ -434,8 +434,6 @@ class App:
# from ba._dependency import test_depset
# test_depset()
if bool(False):
self._test_https()
def _update_state(self) -> None:
if self._app_paused:
@ -654,19 +652,3 @@ class App:
"""
self._initial_login_completed = True
self._update_state()
def _test_https(self) -> None:
"""Testing https support.
(would be nice to get this working on our custom Python builds; need
to wrangle certificates somehow).
"""
import urllib.request
try:
with urllib.request.urlopen('https://example.com') as url:
val = url.read()
_ba.screenmessage('HTTPS SUCCESS!')
print('HTTPS TEST SUCCESS', len(val))
except Exception as exc:
_ba.screenmessage('HTTPS FAIL.')
print('HTTPS TEST FAIL:', exc)

View File

@ -17,6 +17,7 @@ import sys
from efro.dataclassio import (ioprepped, IOAttrs, dataclass_from_json,
dataclass_to_json)
import _ba
if TYPE_CHECKING:
from bacommon.assets import AssetPackageFlavor
@ -165,7 +166,6 @@ def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None:
"""
# pylint: disable=consider-using-with
import socket
# We don't want to keep the provided AssetGather alive, but we want
@ -175,7 +175,9 @@ def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None:
# Pass a very short timeout to urllib so we have opportunities
# to cancel even with network blockage.
req = urllib.request.urlopen(url, timeout=1)
req = urllib.request.urlopen(url,
context=_ba.app.net.sslcontext,
timeout=1)
file_size = int(req.headers['Content-Length'])
print(f'\nDownloading: {filename} Bytes: {file_size:,}')

View File

@ -3,6 +3,7 @@
"""Networking related functionality."""
from __future__ import annotations
import ssl
import copy
import threading
import weakref
@ -34,11 +35,28 @@ class NetworkSubsystem:
# that a nearby server has been pinged.
self.zone_pings: dict[str, float] = {}
self._sslcontext: ssl.SSLContext | None = None
# For debugging.
self.v1_test_log: str = ''
self.v1_ctest_results: dict[int, str] = {}
self.server_time_offset_hours: float | None = None
@property
def sslcontext(self) -> ssl.SSLContext:
"""Create/return our shared SSLContext.
This can be reused for all standard urllib requests/etc.
"""
# Note: I've run into older Android devices taking upwards of 1 second
# to put together a default SSLContext, so recycling one can definitely
# be a worthwhile optimization. This was suggested to me in this
# thread by one of Python's SSL maintainers:
# https://github.com/python/cpython/issues/94637
if self._sslcontext is None:
self._sslcontext = ssl.create_default_context()
return self._sslcontext
def get_ip_address_type(addr: str) -> socket.AddressFamily:
"""Return socket.AF_INET6 or socket.AF_INET4 for the provided address."""
@ -126,6 +144,7 @@ class MasterServerCallThread(threading.Thread):
self._request + '?' +
urllib.parse.urlencode(self._data)), None,
{'User-Agent': _ba.app.user_agent_string}),
context=_ba.app.net.sslcontext,
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS)
elif self._request_type == 'post':
response = urllib.request.urlopen(
@ -133,6 +152,7 @@ class MasterServerCallThread(threading.Thread):
_ba.get_master_server_address() + '/' + self._request,
urllib.parse.urlencode(self._data).encode(),
{'User-Agent': _ba.app.user_agent_string}),
context=_ba.app.net.sslcontext,
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS)
else:
raise TypeError('Invalid request_type: ' + self._request_type)

View File

@ -335,9 +335,12 @@ def _test_v2_time() -> None:
def _test_fetch(baseaddr: str) -> None:
# pylint: disable=consider-using-with
import urllib.request
response = urllib.request.urlopen(urllib.request.Request(
f'{baseaddr}/ping', None, {'User-Agent': _ba.app.user_agent_string}),
timeout=10.0)
response = urllib.request.urlopen(
urllib.request.Request(f'{baseaddr}/ping', None,
{'User-Agent': _ba.app.user_agent_string}),
context=ba.app.net.sslcontext,
timeout=10.0,
)
if response.getcode() != 200:
raise RuntimeError(
f'Got unexpected response code {response.getcode()}.')

View File

@ -84,6 +84,7 @@
<w>axismotion</w>
<w>backgrounded</w>
<w>backgrounding</w>
<w>backporting</w>
<w>backtraces</w>
<w>backwin</w>
<w>ballistica</w>
@ -1255,6 +1256,7 @@
<w>tdels</w>
<w>tdiff</w>
<w>tdlds</w>
<w>tdstpath</w>
<w>tegra</w>
<w>telefonaktiebolaget</w>
<w>teleported</w>
@ -1315,6 +1317,7 @@
<w>trimeshes</w>
<w>trynum</w>
<w>tself</w>
<w>tsrcpath</w>
<w>tunmd</w>
<w>tupleval</w>
<w>tval</w>

View File

@ -11,6 +11,7 @@
;; Projectile indexing and search will ignore the following
;; (in addition to git-ignored stuff which it ignores by default)
(nil . ((projectile-globally-ignored-directories . ("docs"
"submodules"
"src/external"
"assets/src/pylib-android"
"assets/src/pylib-apple"

View File

@ -21,7 +21,7 @@
namespace ballistica {
// These are set automatically via script; don't modify them here.
const int kAppBuildNumber = 20644;
const int kAppBuildNumber = 20645;
const char* kAppVersion = "1.7.4";
// Our standalone globals.

View File

@ -58,7 +58,7 @@ def filter_gradle_file(buildfilename: str, enabled_tags: set[str]) -> None:
line = line[3:]
lines[lineno] = leading + line
# Only write if its not changed (potentially avoid triggering builds).
# Only write if its changed (potentially avoid triggering builds).
out = '\n'.join(lines) + '\n'
if out != original:
with open(buildfilename, 'w', encoding='utf-8') as outfile:

View File

@ -960,3 +960,26 @@ def check_android_studio(projroot: Path, full: bool, verbose: bool) -> None:
displayname='Android Studio',
inspect=inspect,
verbose=verbose)
def sort_jetbrains_dict(original: str) -> str:
"""Given jetbrains dict contents, sort it the way jetbrains would."""
lines = original.splitlines()
if lines[2] != ' <words>':
raise RuntimeError('Unexpected dictionary format.')
if lines[-3] != ' </words>':
raise RuntimeError('Unexpected dictionary format b.')
if not all(
l.startswith(' <w>') and l.endswith('</w>')
for l in lines[3:-3]):
raise RuntimeError('Unexpected dictionary format.')
# Sort lines in the words section.
assert all(l.startswith(' <w>') for l in lines[3:-3])
# Note: need to pull the </w> off the end of the line when sorting
# or it messes with the order and we get different results than
# Jetbrains stuff.
return '\n'.join(lines[:3] +
sorted(lines[3:-3], key=lambda x: x.replace('</w>', '')) +
lines[-3:])

View File

@ -115,6 +115,7 @@ def _trim_docstring(docstring: str) -> str:
def _spelling(words: list[str]) -> None:
from efrotools.code import sort_jetbrains_dict
import os
num_modded_dictionaries = 0
for fname in [
@ -135,16 +136,8 @@ def _spelling(words: list[str]) -> None:
added_count += 1
with open(fname, 'w', encoding='utf-8') as outfile:
# Sort lines in the words section.
assert all(l.startswith(' <w>') for l in lines[3:-3])
outfile.write(sort_jetbrains_dict('\n'.join(lines)))
# Note: need to pull the </w> off the end of the line when sorting
# or it messes with the order and we get different results than
# Jetbrains stuff.
outfile.write('\n'.join(
lines[:3] +
sorted(lines[3:-3], key=lambda x: x.replace('</w>', '')) +
lines[-3:]))
print(f'Added {added_count} words to {fname}.')
num_modded_dictionaries += 1
print(f'Modified {num_modded_dictionaries} dictionaries.')