gawel / wsgiproxy2 Goto Github PK
View Code? Open in Web Editor NEWA WSGI Proxy with various http client backends
Home Page: https://wsgiproxy2.readthedocs.org/en/latest/
License: MIT License
A WSGI Proxy with various http client backends
Home Page: https://wsgiproxy2.readthedocs.org/en/latest/
License: MIT License
The metadata in setup.py indicates the license is Expat (aka MIT), but no license file is provided. Please provide one!
"setup.py build" -> "setup.py install --root </install/prefix>" -> pytest with PYTHONPATH pointing to python stearch and sitelib inside </install/prefix>
+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-WSGIProxy2-0.4.6-8.fc35.x86_64/usr/lib64/python3.8/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-WSGIProxy2-0.4.6-8.fc35.x86_64/usr/lib/python3.8/site-packages
+ PYTHONDONTWRITEBYTECODE=1
+ /usr/bin/pytest -ra
=========================================================================== test session starts ============================================================================
platform linux -- Python 3.8.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /home/tkloczko/rpmbuild/BUILD/WSGIProxy2-0.4.6
plugins: forked-1.3.0, shutil-1.7.0, virtualenv-1.7.0, expect-1.1.0, httpbin-1.0.0, xdist-2.2.1, flake8-1.0.7, timeout-1.4.2, betamax-0.8.1, freezegun-0.4.2, case-1.5.3, isort-1.3.0, aspectlib-1.5.2, asyncio-0.15.1, toolbox-0.5, xprocess-0.17.1, aiohttp-0.3.0, checkdocs-2.7.0, mock-3.6.1, rerunfailures-9.1.1, requests-mock-1.9.3, Faker-8.4.0, cov-2.12.1, pyfakefs-4.5.0, hypothesis-6.13.14, cases-3.6.1
collected 29 items
. . [ 3%]
wsgiproxy/test_wsgiproxy.py ........................... [100%]
============================================================================= warnings summary =============================================================================
../../../../../usr/lib/python3.8/site-packages/webtest/app.py:88
/usr/lib/python3.8/site-packages/webtest/app.py:88: PytestCollectionWarning: cannot collect test class 'TestApp' because it has a __init__ constructor (from: wsgiproxy/test_wsgiproxy.py)
class TestApp(object):
wsgiproxy/test_wsgiproxy.py: 21 warnings
/usr/lib/python3.8/site-packages/waitress/adjustments.py:441: DeprecationWarning: In future versions of Waitress clear_untrusted_proxy_headers will be set to True by default. You may opt-out by setting this value to False, or opt-in explicitly by setting this to True.
warnings.warn(
-- Docs: https://docs.pytest.org/en/stable/warnings.html
===================================================================== 28 passed, 22 warnings in 6.90s ======================================================================
+ /usr/bin/python3 setup.py build_sphinx -b man --build-dir build/sphinx
running build_sphinx
Running Sphinx v4.0.2
making output directory... done
WARNING: html_static_path entry '_static' does not exist
building [mo]: targets for 0 po files that are out of date
building [man]: all manpages
updating environment: [new config] 3 added, 0 changed, 0 removed
reading sources... [100%] proxies
WARNING: autodoc: failed to import class 'restkit_client.HttpClient' from module 'wsgiproxy'; the following exception was raised:
No module named 'wsgiproxy.restkit_client'
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
writing... python-WSGIProxy2.3 { proxies clients } done
build succeeded, 2 warnings.
Some tests (test_chunked
, test_form
, test_redirect
and test_status
) in wsgiproxy.tests.TestRequests
hang with Python 3.2 and 3.3.
This problem occurs in WSGIProxy2 0.3 and does not occur in WSGIProxy2 0.2.
(In WSGIProxy2 0.2, test_form
and test_redirect
fail, without hanging.)
I use Requests 1.2.3.
test_proxy (wsgiproxy.tests.TestExtractUri) ... ok
test_transparent_proxy (wsgiproxy.tests.TestExtractUri) ... ok
test_chunked (wsgiproxy.tests.TestHttplib) ... ok
test_form (wsgiproxy.tests.TestHttplib) ... ok
test_head (wsgiproxy.tests.TestHttplib) ... ok
test_not_allowed_method (wsgiproxy.tests.TestHttplib) ... ok
test_redirect (wsgiproxy.tests.TestHttplib) ... ok
test_status (wsgiproxy.tests.TestHttplib) ... ok
test_webob_error (wsgiproxy.tests.TestHttplib) ... ok
test_exception (wsgiproxy.tests.TestMisc) ... ok
test_rewrite_location (wsgiproxy.tests.TestMisc) ... ok
test_socket_gaierror (wsgiproxy.tests.TestMisc) ... ok
test_socket_timeout (wsgiproxy.tests.TestMisc) ... ok
test_chunked (wsgiproxy.tests.TestRequests) ...
I'm getting a resource warning (socket not being closed) when using WSGIProxy2 with bottle.
It looks like the connection is not being explicitly closed in HttpClient.call. I tried adding a 'conn.close()' before the return and it appears to have fixed the warning.
It does not remove a gzip header though! Nor should it IMO, it should just pass the data through.
iter_content
in requests does not support this, because it always sets decode_content=True
Options include:
As hardcoded in proxies.py, OPTIONS is not one of the ALLOWED_METHODS.
This prevents me from fully testing my app via webtest (which uses wsgiproxy in one of its test modes).
Is there anything preventing you from adding it?
README_fixt.py
is missing in WSGIProxy2-0.4.zip available on PyPI, which results in failure of doctests.
$ nosetests-2.7
nose.config: INFO: Ignoring files matching ['^\\.', '^_', '^setup\\.py$']
nose.plugins.cover: INFO: Coverage report will include only packages: ['wsgiproxy']
Doctest: README.rst ... FAIL
nose.selector: INFO: /tmp/WSGIProxy2-0.4/wsgiproxy/restkit_client.py is executable; skipped
test_proxy (wsgiproxy.tests.TestExtractUri) ... ok
test_transparent_proxy (wsgiproxy.tests.TestExtractUri) ... ok
test_chunked (wsgiproxy.tests.TestHttplib) ... ok
test_form (wsgiproxy.tests.TestHttplib) ... ok
test_head (wsgiproxy.tests.TestHttplib) ... ok
test_not_allowed_method (wsgiproxy.tests.TestHttplib) ... ok
test_redirect (wsgiproxy.tests.TestHttplib) ... ok
test_status (wsgiproxy.tests.TestHttplib) ... ok
test_webob_error (wsgiproxy.tests.TestHttplib) ... ok
test_exception (wsgiproxy.tests.TestMisc) ... ok
test_rewrite_location (wsgiproxy.tests.TestMisc) ... ok
test_socket_gaierror (wsgiproxy.tests.TestMisc) ... ok
test_socket_timeout (wsgiproxy.tests.TestMisc) ... ok
test_chunked (wsgiproxy.tests.TestRequests) ... ok
test_form (wsgiproxy.tests.TestRequests) ... ok
test_head (wsgiproxy.tests.TestRequests) ... ok
test_not_allowed_method (wsgiproxy.tests.TestRequests) ... ok
test_redirect (wsgiproxy.tests.TestRequests) ... ok
test_status (wsgiproxy.tests.TestRequests) ... ok
test_webob_error (wsgiproxy.tests.TestRequests) ... ok
test_chunked (wsgiproxy.tests.TestRestkit) ... ok
test_form (wsgiproxy.tests.TestRestkit) ... ok
test_head (wsgiproxy.tests.TestRestkit) ... ok
test_not_allowed_method (wsgiproxy.tests.TestRestkit) ... ok
test_redirect (wsgiproxy.tests.TestRestkit) ... ok
test_status (wsgiproxy.tests.TestRestkit) ... ok
test_webob_error (wsgiproxy.tests.TestRestkit) ... ok
test_chunked (wsgiproxy.tests.TestUrllib3) ... ok
test_form (wsgiproxy.tests.TestUrllib3) ... ok
test_head (wsgiproxy.tests.TestUrllib3) ... ok
test_not_allowed_method (wsgiproxy.tests.TestUrllib3) ... ok
test_redirect (wsgiproxy.tests.TestUrllib3) ... ok
test_status (wsgiproxy.tests.TestUrllib3) ... ok
test_webob_error (wsgiproxy.tests.TestUrllib3) ... ok
======================================================================
FAIL: Doctest: README.rst
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib64/python2.7/doctest.py", line 2226, in runTest
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for README.rst
File "/tmp/WSGIProxy2-0.4/README.rst", line 0
----------------------------------------------------------------------
File "/tmp/WSGIProxy2-0.4/README.rst", line 19, in README.rst
Failed example:
proxy = HostProxy(application_url)
Exception raised:
Traceback (most recent call last):
File "/usr/lib64/python2.7/doctest.py", line 1315, in __run
compileflags, 1) in test.globs
File "<doctest README.rst[1]>", line 1, in <module>
proxy = HostProxy(application_url)
NameError: name 'application_url' is not defined
----------------------------------------------------------------------
File "/tmp/WSGIProxy2-0.4/README.rst", line 26, in README.rst
Failed example:
resp = req.get_response(proxy)
Exception raised:
Traceback (most recent call last):
File "/usr/lib64/python2.7/doctest.py", line 1315, in __run
compileflags, 1) in test.globs
File "<doctest README.rst[4]>", line 1, in <module>
resp = req.get_response(proxy)
NameError: name 'proxy' is not defined
----------------------------------------------------------------------
File "/tmp/WSGIProxy2-0.4/README.rst", line 27, in README.rst
Failed example:
print(resp.text)
Exception raised:
Traceback (most recent call last):
File "/usr/lib64/python2.7/doctest.py", line 1315, in __run
compileflags, 1) in test.globs
File "<doctest README.rst[5]>", line 1, in <module>
print(resp.text)
NameError: name 'resp' is not defined
----------------------------------------------------------------------
File "/tmp/WSGIProxy2-0.4/README.rst", line 39, in README.rst
Failed example:
proxy = HostProxy(application_url, client='urllib3')
Exception raised:
Traceback (most recent call last):
File "/usr/lib64/python2.7/doctest.py", line 1315, in __run
compileflags, 1) in test.globs
File "<doctest README.rst[6]>", line 1, in <module>
proxy = HostProxy(application_url, client='urllib3')
NameError: name 'application_url' is not defined
----------------------------------------------------------------------
File "/tmp/WSGIProxy2-0.4/README.rst", line 43, in README.rst
Failed example:
proxy = HostProxy(application_url, client='requests')
Exception raised:
Traceback (most recent call last):
File "/usr/lib64/python2.7/doctest.py", line 1315, in __run
compileflags, 1) in test.globs
File "<doctest README.rst[7]>", line 1, in <module>
proxy = HostProxy(application_url, client='requests')
NameError: name 'application_url' is not defined
Name Stmts Miss Cover Missing
---------------------------------------------------------
wsgiproxy 3 0 100%
wsgiproxy.proxies 157 0 100%
wsgiproxy.requests_client 28 2 93% 26, 32
wsgiproxy.restkit_client 8 0 100%
wsgiproxy.urllib3_client 21 0 100%
---------------------------------------------------------
TOTAL 217 2 99%
----------------------------------------------------------------------
Ran 35 tests in 3.701s
FAILED (failures=1)
Just hit the issue fixed in #22. I worked around it, but it would nice to have that fix in a release 😄
Similarly to #15, PATCH method is also not mentioned in list of allowed methods: https://github.com/gawel/WSGIProxy2/blob/master/wsgiproxy/proxies.py#L26
This is similar to #18 in that it deals with behavior which the HTTP client "fixes" in the response body.
This issue applies at least to the 'requests' client as well as the 'httplib' client. When the source response includes a chunked Transfer-Encoding, this header is relayed to the destination response but the client library has stripped off the chunk framing so the destination response is inconsistent (chunked T-E but non-chunked body).
I'm using WSGIProxy2 as part of WebTest and am seeing errors when using quoted unicode urls. I'll show the stack of what is going on at the point a UnicodeDecodeError is raised:
test_function():
testapp = webtest.TestApp(server_url)
path = '/targets/NR2F1%C3%82-human/'
-> res = testapp.get(path, status=200)
eggs/WebTest-2.0.16-py3.4.egg/webtest/app.py(321)get()
-> expect_errors=expect_errors)
(Pdb) args
url = '/targets/NR2F1%C3%82-human/'
(Pdb) p req.environ['PATH_INFO']
'/targets/NR2F1Ã\x82-human/'
The path is unquoted by webob.request.environ_from_url(path) which for Python 3 is::
def url_unquote(s):
return unquote(s.encode('ascii')).decode('latin-1')
eggs/WebTest-2.0.16-py3.4.egg/webtest/app.py(604)do_request()
-> res = req.get_response(app, catch_exc_info=True)
eggs/WebOb-1.4-py3.4.egg/webob/request.py(1316)send()
-> application, catch_exc_info=True)
eggs/WebOb-1.4-py3.4.egg/webob/request.py(1284)call_application()
-> app_iter = application(self.environ, start_response)
eggs/WebTest-2.0.16-py3.4.egg/webtest/lint.py(198)lint_app()
-> iterator = application(environ, start_response_wrapper)
eggs/WSGIProxy2-0.4.1-py3.4.egg/wsgiproxy/proxies.py(182)__call__()
-> response = self.process_request(uri, method, new_headers, environ)
(Pdb) pp environ['PATH_INFO']
'/targets/NR2F1Ã\x82-human/'
eggs/WSGIProxy2-0.4.1-py3.4.egg/wsgiproxy/proxies.py(134)process_request()
-> return self.http(uri, method, environ['wsgi.input'], headers)
eggs/WSGIProxy2-0.4.1-py3.4.egg/wsgiproxy/proxies.py(79)__call__()
-> conn.request(method, path, body, headers, **self.options)
lib/python3.4/http/client.py(1090)request()
-> self._send_request(method, url, body, headers)
lib/python3.4/http/client.py(1118)_send_request()
-> self.putrequest(method, url, **skips)
lib/python3.4/http/client.py(975)putrequest()
request = '%s %s %s' % (method, url, self._http_vsn_str)
# Non-ASCII characters should have been eliminated earlier
-> self._output(request.encode('ascii'))
(Pdb) args
url = '/targets/NR2F1Ã\x82-human/'
(Pdb) p request
'GET /targets/NR2F1Ã\x82-human/ HTTP/1.1'
(Pdb) p request.encode('ascii')
*** UnicodeEncodeError: 'ascii' codec can't encode characters in position 18-19: ordinal not in range(128)
From pyramid.compat for reference:
if PY3: # pragma: no cover
# see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before
# decoding it to utf-8
def decode_path_info(path):
return path.encode('latin-1').decode('utf-8')
else:
def decode_path_info(path):
return path.decode('utf-8')
if PY3: # pragma: no cover
# see PEP 3333 for why we decode the path to latin-1
from urllib.parse import unquote_to_bytes
def unquote_bytes_to_wsgi(bytestring):
return unquote_to_bytes(bytestring).decode('latin-1')
else:
from urlparse import unquote as unquote_to_bytes
def unquote_bytes_to_wsgi(bytestring):
return unquote_to_bytes(bytestring)
(Pdb) from pyramid.compat import unquote_bytes_to_wsgi, decode_path_info
(Pdb) p unquote_bytes_to_wsgi(path)
'/targets/NR2F1Ã\x82-human/'
(Pdb) from webob.compat import url_unquote, url_quote
(Pdb) url_unquote(path)
'/targets/NR2F1Ã\x82-human/'
(Pdb) decode_path_info(url_unquote(path))
'/targets/NR2F1Â-human/'
(Pdb) url_quote(url_unquote(path))
'/targets/NR2F1%C3%83%C2%82-human/'
We'll have to be careful with the requoting. I'll see if I can unpick those parts soon and add to the ticket.
As of 0.4.6, if the request environ
contains a RAW_URI
or REQUEST_URI
value it is used directly, ignoring the "strip_script_name" constructor parameter.
This makes the proxy have different behavior for WSGI servers which set RAW_URI
or REQUEST_URI
from those which do not. It seems like a simple fix would be to detect-and-remove the environ['SCRIPT_NAME']
which is a PEP-333 required variable. The other '_URI' environ names are not PEP-333 standard.
As a workaround, a WSGI middleware can strip REQUEST_URI
values.
When I tried to use WSGIProxy2 with Werkzeug’s test client, I noticed the query string gets lost. Here’s how to reproduce:
import werkzeug.test
import wsgiproxy
proxy = wsgiproxy.HostProxy("http://localhost:8080")
client = werkzeug.test.Client(proxy)
client.get("/x?a=b")
If you have an HTTP server running at localhost:8080, you’ll notice the query string is missing in the access log.
The issue is located here: https://github.com/gawel/WSGIProxy2/blob/0.4.6/wsgiproxy/proxies.py#L149-L166
It’s caused by Werkzeug’s definition of REQUEST_URI which does not contain the query string. I think this is unusual, but since REQUEST_URI is not standardized they’re not objectively wrong. It’s hard to please every WSGI server, each with their own interpretation of the environment, but PEP 3333 is clear: https://www.python.org/dev/peps/pep-3333/#url-reconstruction
Python’s standard library also provide an URL reconstruction method that should be a good fit: https://docs.python.org/3.8/library/wsgiref.html#wsgiref.util.request_uri
I don’t know why in WSGIProxy2 the non-standard REQUEST_URI and RAW_URI take precedence over the standard variables, but I daresay implementations conforming to PEP 3333 should work. However, if for some reason you had to make it that way, how about adding the query string after REQUEST_URI when it doesn’t already contain one even though QUERY_STRING is not empty?
In testsuite of wsgiproxy2-0.4.2 under py3. there are errorsX3;
test_quoted_utf8_url, test_quoted_utf8_url & test_quoted_utf8_url.
======================================================================
ERROR: test_quoted_utf8_url (wsgiproxy.tests.TestHttplib)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/gen2/TmpDir/portage/dev-python/wsgiproxy2-0.4.2/work/WSGIProxy2-0.4.2/wsgiproxy/tests.py", line 103, in test_quoted_utf8_url
resp = self.app.get(path)
File "/usr/lib64/python3.4/site-packages/webtest/app.py", line 286, in get
expect_errors=expect_errors)
File "/usr/lib64/python3.4/site-packages/webtest/app.py", line 595, in do_request
self._check_status(status, res)
File "/usr/lib64/python3.4/site-packages/webtest/app.py", line 624, in _check_status
res)
webtest.app.AppError: Bad response: 500 Internal Server Error (not 200 OK or 3xx redirect for http://127.0.0.1:50200/targets/NR2F1%C3%82-human/)
b'Internal Server Error\r\n\r\nTraceback (most recent call last):\n File "/usr/lib64/python3.4/site-packages/waitress/channel.py", line 337, in service\n task.service()\n File "/usr/lib64/python3.4/site-packages/waitress/task.py", line 173, in service\n self.execute()\n File "/usr/lib64/python3.4/site-packages/waitress/task.py", line 392, in execute\n app_iter = self.channel.server.application(env, start_response)\n File "/usr/lib64/python3.4/site-packages/webtest/http.py", line 83, in wrapper\n return self.test_app(environ, start_response)\n File "/usr/lib64/python3.4/site-packages/webtest/debugapp.py", line 49, in __call__\n body = body.encode(\'ascii\')\nUnicodeEncodeError: \'ascii\' codec can\'t encode characters in position 151-152: ordinal not in range(128)\n\r\n\r\n(generated by waitress)'
======================================================================
ERROR: test_quoted_utf8_url (wsgiproxy.tests.TestRequests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/gen2/TmpDir/portage/dev-python/wsgiproxy2-0.4.2/work/WSGIProxy2-0.4.2/wsgiproxy/tests.py", line 103, in test_quoted_utf8_url
resp = self.app.get(path)
File "/usr/lib64/python3.4/site-packages/webtest/app.py", line 286, in get
expect_errors=expect_errors)
File "/usr/lib64/python3.4/site-packages/webtest/app.py", line 595, in do_request
self._check_status(status, res)
File "/usr/lib64/python3.4/site-packages/webtest/app.py", line 624, in _check_status
res)
nose.proxy.AppError: Bad response: 500 Internal Server Error (not 200 OK or 3xx redirect for http://127.0.0.1:54658/targets/NR2F1%C3%82-human/)
b'Internal Server Error\r\n\r\nTraceback (most recent call last):\n File "/usr/lib64/python3.4/site-packages/waitress/channel.py", line 337, in service\n task.service()\n File "/usr/lib64/python3.4/site-packages/waitress/task.py", line 173, in service\n self.execute()\n File "/usr/lib64/python3.4/site-packages/waitress/task.py", line 392, in execute\n app_iter = self.channel.server.application(env, start_response)\n File "/usr/lib64/python3.4/site-packages/webtest/http.py", line 83, in wrapper\n return self.test_app(environ, start_response)\n File "/usr/lib64/python3.4/site-packages/webtest/debugapp.py", line 49, in __call__\n body = body.encode(\'ascii\')\nUnicodeEncodeError: \'ascii\' codec can\'t encode characters in position 276-277: ordinal not in range(128)\n\r\n\r\n(generated by waitress)'
-------------------- >> begin captured logging << --------------------
requests.packages.urllib3.connectionpool: INFO: Starting new HTTP connection (1): 127.0.0.1
requests.packages.urllib3.connectionpool: DEBUG: "GET /targets/NR2F1%C3%82-human/ HTTP/1.1" 500 799
--------------------- >> end captured logging << ---------------------
======================================================================
ERROR: test_quoted_utf8_url (wsgiproxy.tests.TestUrllib3)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/gen2/TmpDir/portage/dev-python/wsgiproxy2-0.4.2/work/WSGIProxy2-0.4.2/wsgiproxy/tests.py", line 103, in test_quoted_utf8_url
resp = self.app.get(path)
File "/usr/lib64/python3.4/site-packages/webtest/app.py", line 286, in get
expect_errors=expect_errors)
File "/usr/lib64/python3.4/site-packages/webtest/app.py", line 595, in do_request
self._check_status(status, res)
File "/usr/lib64/python3.4/site-packages/webtest/app.py", line 624, in _check_status
res)
nose.proxy.AppError: Bad response: 500 Internal Server Error (not 200 OK or 3xx redirect for http://127.0.0.1:50848/targets/NR2F1%C3%82-human/)
b'Internal Server Error\r\n\r\nTraceback (most recent call last):\n File "/usr/lib64/python3.4/site-packages/waitress/channel.py", line 337, in service\n task.service()\n File "/usr/lib64/python3.4/site-packages/waitress/task.py", line 173, in service\n self.execute()\n File "/usr/lib64/python3.4/site-packages/waitress/task.py", line 392, in execute\n app_iter = self.channel.server.application(env, start_response)\n File "/usr/lib64/python3.4/site-packages/webtest/http.py", line 83, in wrapper\n return self.test_app(environ, start_response)\n File "/usr/lib64/python3.4/site-packages/webtest/debugapp.py", line 49, in __call__\n body = body.encode(\'ascii\')\nUnicodeEncodeError: \'ascii\' codec can\'t encode characters in position 169-170: ordinal not in range(128)\n\r\n\r\n(generated by waitress)'
-------------------- >> begin captured logging << --------------------
urllib3.connectionpool: INFO: Starting new HTTP connection (1): 127.0.0.1
urllib3.connectionpool: DEBUG: "GET /targets/NR2F1%C3%82-human/ HTTP/1.1" 500 799
--------------------- >> end captured logging << ---------------------
Name Stmts Miss Cover Missing
---------------------------------------------------------
wsgiproxy 3 0 100%
wsgiproxy.proxies 161 0 100%
wsgiproxy.requests_client 28 2 93% 26, 32
wsgiproxy.urllib3_client 21 0 100%
---------------------------------------------------------
TOTAL 213 2 99%
----------------------------------------------------------------------
Ran 31 tests in 3.388s
FAILED (errors=3)
They are all UnicodeEncode type Errors re utf under py3. They all source immediately to
the system installed /waitress/channel.py", line 337. It's looking like code inadequately converted or ported to python3. I haven't had another user replicate yet. Can you replicate?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.