Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf5bd551be | ||
|
|
1bf705e4ee | ||
|
|
3655bd8027 | ||
|
|
a29febde5c | ||
|
|
677ea6ab95 | ||
|
|
23fd33b344 | ||
|
|
a2cf6b70c1 | ||
|
|
8d7081e747 | ||
|
|
9373eb8f4d | ||
|
|
8339a76cc1 | ||
|
|
8054e64ad7 | ||
|
|
4d08291813 | ||
|
|
bfd7e64d9f | ||
|
|
aa99cc1cd4 | ||
|
|
01167cb136 | ||
|
|
b157045a37 | ||
|
|
bf7a36f03e | ||
|
|
c5b7e331da | ||
|
|
e6141ac6ac | ||
|
|
0754d30550 | ||
|
|
51e8ca782b | ||
|
|
999f1063d0 | ||
|
|
61dd957018 | ||
|
|
f88378a8da | ||
|
|
0115a63898 | ||
|
|
53e28ef0c1 | ||
|
|
77e3f140ca | ||
|
|
fb0c343161 | ||
|
|
7032606103 | ||
|
|
aea0524bb5 | ||
|
|
078bbb1fdc | ||
|
|
fbcde8774b | ||
|
|
bd3356071e | ||
|
|
b63f9bc718 | ||
|
|
896afc0141 | ||
|
|
4f4bd7bd06 | ||
|
|
7fb02bb768 | ||
|
|
6a2b9c31b1 | ||
|
|
07c2fdc693 | ||
|
|
b275aa7123 | ||
|
|
10a80333da | ||
|
|
09953ac19a |
4
.travis.yml
Normal file
4
.travis.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
script: make release-all
|
||||
install: true
|
||||
go:
|
||||
- 1.1
|
||||
5
CONTRIBUTORS
Normal file
5
CONTRIBUTORS
Normal file
@@ -0,0 +1,5 @@
|
||||
Contributors to ngrok, both large and small:
|
||||
Alan Shreve (inconshreveable)
|
||||
Kyle Conroy (kyleconroy)
|
||||
Caleb Spare (cespare)
|
||||
Stephen Huenneke (skastel)
|
||||
8
Makefile
8
Makefile
@@ -16,11 +16,13 @@ client: deps
|
||||
go install -tags '$(BUILDTAGS)' ngrok/main/ngrok
|
||||
|
||||
client-assets:
|
||||
go install github.com/inconshreveable/go-bindata
|
||||
go get github.com/inconshreveable/go-bindata
|
||||
GOOS="" GOARCH="" go install github.com/inconshreveable/go-bindata
|
||||
bin/go-bindata -o src/ngrok/client/assets assets/client
|
||||
|
||||
server-assets:
|
||||
go install github.com/inconshreveable/go-bindata
|
||||
go get github.com/inconshreveable/go-bindata
|
||||
GOOS="" GOARCH="" go install github.com/inconshreveable/go-bindata
|
||||
bin/go-bindata -o src/ngrok/server/assets assets/server
|
||||
|
||||
release-client: BUILDTAGS=release
|
||||
@@ -34,4 +36,4 @@ release-all: release-client release-server
|
||||
all: fmt client server
|
||||
|
||||
clean:
|
||||
go clean ngrok/...
|
||||
go clean -i -r ngrok/...
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<script src="/static/js/jquery-1.9.1.min.js"></script>
|
||||
<script src="/static/js/jquery.timeago.js"></script>
|
||||
<script src="/static/js/angular.js"></script>
|
||||
<script src="/static/js/angular-sanitize.min.js"></script>
|
||||
<script src="/static/js/base64.js"></script>
|
||||
<script src="/static/js/ngrok.js"></script>
|
||||
<script type="text/javascript">
|
||||
@@ -18,8 +19,8 @@
|
||||
table.params { font-size: 12px; font-family: Courier, monospace; }
|
||||
.txn-selector tr { cursor: pointer; }
|
||||
.txn-selector tr:hover { background-color: #ddd; }
|
||||
tr.selected, tr.selected:hover {
|
||||
background-color: #ff9999;
|
||||
tr.selected, tr.selected:hover {
|
||||
background-color: #ff9999;
|
||||
background-color: #000000;
|
||||
color:white;
|
||||
|
||||
@@ -70,11 +71,11 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<i class="icon-time"></i> Duration
|
||||
<i class="icon-time"></i> Duration
|
||||
<span style="margin-left: 8px;" class="muted">{{Txn.Duration}}</span>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<i class="icon-user"></i> IP
|
||||
<i class="icon-user"></i> IP
|
||||
<span style="margin-left: 8px;" class="muted">{{Txn.Req.Header['X-Real-Ip'][0]}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
13
assets/client/static/js/angular-sanitize.min.js
vendored
Normal file
13
assets/client/static/js/angular-sanitize.min.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
AngularJS v1.1.5
|
||||
(c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(I,h){'use strict';function i(a){var d={},a=a.split(","),c;for(c=0;c<a.length;c++)d[a[c]]=!0;return d}function z(a,d){function c(a,b,c,f){b=h.lowercase(b);if(m[b])for(;e.last()&&n[e.last()];)g("",e.last());o[b]&&e.last()==b&&g("",b);(f=p[b]||!!f)||e.push(b);var j={};c.replace(A,function(a,b,d,c,g){j[b]=k(d||c||g||"")});d.start&&d.start(b,j,f)}function g(a,b){var c=0,g;if(b=h.lowercase(b))for(c=e.length-1;c>=0;c--)if(e[c]==b)break;if(c>=0){for(g=e.length-1;g>=c;g--)d.end&&d.end(e[g]);e.length=
|
||||
c}}var b,f,e=[],j=a;for(e.last=function(){return e[e.length-1]};a;){f=!0;if(!e.last()||!q[e.last()]){if(a.indexOf("<\!--")===0)b=a.indexOf("--\>"),b>=0&&(d.comment&&d.comment(a.substring(4,b)),a=a.substring(b+3),f=!1);else if(B.test(a)){if(b=a.match(r))a=a.substring(b[0].length),b[0].replace(r,g),f=!1}else if(C.test(a)&&(b=a.match(s)))a=a.substring(b[0].length),b[0].replace(s,c),f=!1;f&&(b=a.indexOf("<"),f=b<0?a:a.substring(0,b),a=b<0?"":a.substring(b),d.chars&&d.chars(k(f)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+
|
||||
e.last()+"[^>]*>","i"),function(a,b){b=b.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(b));return""}),g("",e.last());if(a==j)throw"Parse Error: "+a;j=a}g()}function k(a){l.innerHTML=a.replace(/</g,"<");return l.innerText||l.textContent||""}function t(a){return a.replace(/&/g,"&").replace(F,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"<").replace(/>/g,">")}function u(a){var d=!1,c=h.bind(a,a.push);return{start:function(a,b,f){a=h.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]==
|
||||
!0&&(c("<"),c(a),h.forEach(b,function(a,b){var d=h.lowercase(b);if(G[d]==!0&&(w[d]!==!0||a.match(H)))c(" "),c(b),c('="'),c(t(a)),c('"')}),c(f?"/>":">"))},end:function(a){a=h.lowercase(a);!d&&v[a]==!0&&(c("</"),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d||c(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^</,B=/^<\s*\//,D=/<\!--(.*?)--\>/g,
|
||||
E=/<!\[CDATA\[(.*?)]]\>/g,H=/^((ftp|https?):\/\/|mailto:|tel:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=h.extend({},y,x),m=h.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=h.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),
|
||||
q=i("script,style"),v=h.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=h.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");h.module("ngSanitize",[]).value("$sanitize",function(a){var d=[];
|
||||
z(a,u(d));return d.join("")});h.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,c,g){c.addClass("ng-binding").data("$binding",g.ngBindHtml);d.$watch(g.ngBindHtml,function(b){b=a(b);c.html(b||"")})}}]);h.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(c,g){if(!c)return c;var b,f=c,e=[],j=u(e),i,k,l={};if(h.isDefined(g))l.target=g;for(;b=f.match(a);)i=
|
||||
b[0],b[2]==b[3]&&(i="mailto:"+i),k=b.index,j.chars(f.substr(0,k)),l.href=i,j.start("a",l),j.chars(b[0].replace(d,"")),j.end("a"),f=f.substring(k+b[0].length);j.chars(f);return e.join("")}})})(window,window.angular);
|
||||
@@ -1,4 +1,4 @@
|
||||
var ngrok = angular.module("ngrok", []);
|
||||
var ngrok = angular.module("ngrok", ["ngSanitize"]);
|
||||
|
||||
var hexRepr = function(bytes) {
|
||||
var buf = [];
|
||||
@@ -47,7 +47,7 @@ ngrok.factory("txnSvc", function() {
|
||||
body.exists = body.Length > 0;
|
||||
body.hasError = !!body.Error;
|
||||
|
||||
body.syntaxClass = {
|
||||
var syntaxClass = {
|
||||
"text/xml": "xml",
|
||||
"application/xml": "xml",
|
||||
"text/html": "xml",
|
||||
@@ -63,12 +63,12 @@ ngrok.factory("txnSvc", function() {
|
||||
} else {
|
||||
body.Text = Base64.decode(body.Text).text;
|
||||
}
|
||||
|
||||
|
||||
// prettify
|
||||
var transform = {
|
||||
"xml": "xml",
|
||||
"json": "json"
|
||||
}[body.syntaxClass];
|
||||
}[syntaxClass];
|
||||
|
||||
if (!body.hasError && !!transform) {
|
||||
try {
|
||||
@@ -79,6 +79,13 @@ ngrok.factory("txnSvc", function() {
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!!syntaxClass) {
|
||||
body.Text = hljs.highlight(syntaxClass, body.Text).value;
|
||||
} else {
|
||||
// highlight.js doesn't have a 'plaintext' syntax, so we'll just copy its escaping function.
|
||||
body.Text = body.Text.replace(/&/gm, '&').replace(/</gm, '<').replace(/>/gm, '>');
|
||||
}
|
||||
};
|
||||
|
||||
var processReq = function(req) {
|
||||
@@ -214,7 +221,7 @@ ngrok.directive({
|
||||
"onbtnclick": "&"
|
||||
},
|
||||
replace: true,
|
||||
template: '' +
|
||||
template: '' +
|
||||
'<ul class="nav nav-pills">' +
|
||||
'<li ng-repeat="tab in tabNames" ng-class="{\'active\': isTab(tab)}">' +
|
||||
'<a href="" ng-click="setTab(tab)">{{tab}}</a>' +
|
||||
@@ -247,7 +254,7 @@ ngrok.directive({
|
||||
'</h6>' +
|
||||
'' +
|
||||
'<div ng-show="!body.isForm && !body.binary">' +
|
||||
'<pre ng-show="body.exists"><code ng-class="body.syntaxClass">{{ body.Text }}</code></pre>' +
|
||||
'<pre ng-show="body.exists"><code ng-bind-html="body.Text"></code></pre>' +
|
||||
'</div>' +
|
||||
'' +
|
||||
'<div ng-show="body.isForm">' +
|
||||
@@ -259,13 +266,6 @@ ngrok.directive({
|
||||
|
||||
link: function($scope, $elem) {
|
||||
$scope.$watch(function() { return $scope.body; }, function() {
|
||||
$code = $elem.find("code");
|
||||
// if we highlight when the code is empty, hljs manipulates the dom in a
|
||||
// a bad way that causes angular to fail
|
||||
if (!!$code.text()) {
|
||||
hljs.highlightBlock($code.get(0));
|
||||
}
|
||||
|
||||
if ($scope.body && $scope.body.ErrorOffset > -1) {
|
||||
var offset = $scope.body.ErrorOffset;
|
||||
|
||||
@@ -317,7 +317,7 @@ ngrok.controller({
|
||||
txnSvc.add(message.data);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
ws.onerror = function(err) {
|
||||
console.log("Web socket error:" + err);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,51 @@
|
||||
# Changelog
|
||||
## 0.21 - 08/17/2013
|
||||
- IMPROVEMENT: The ngrok web ui can now be disabled with -webport=-1
|
||||
|
||||
## 0.20 - 08/17/2013
|
||||
- BUGFIX: Fixed a bug where ngrok would not stop its autoupdate loop even after it should stop
|
||||
|
||||
## 0.19 - 08/17/2013
|
||||
- BUGFIX: Fixed a bug where ngrok's would loop infinitely trying to checking for updates after the second update check
|
||||
- BUGFIX: Fixed a race condition in ngrokd's metrics logging immediately after start up
|
||||
|
||||
## 0.18 - 08/15/2013
|
||||
- BUGFIX: Fixed a bug where ngrok would compare the Host header for virtual hosting using case-sensitive comparisons
|
||||
- BUGFIX: Fixed a bug where ngrok would not include the port number in the virtual host when not serving on port 80
|
||||
- BUGFIX: Fixed a bug where ngrok would crash when trying to replay a request
|
||||
- IMPROVEMENT: ngrok can now indicate manual updates again
|
||||
- IMPROVEMENT: ngrok can now supports update channels
|
||||
- IMPROVEMENT: ngrok can now detect some updates that will fail before downloading
|
||||
|
||||
## 0.17 - 07/30/2013
|
||||
- BUGFIX: Fixed an issue where ngrok's registry cache would return a URL from a different protocol
|
||||
|
||||
## 0.16 - 07/30/2013
|
||||
- BUGFIX: Fixed an issue where ngrok would crash when parsing bad XML that wasn't a syntax error
|
||||
- BUGFIX: Fixed an issue where ngrok would crash when parsing bad JSON that wasn't a syntax error
|
||||
- BUGFIX: Fixed an issue where the web ui would sometimes not update the request body when changing requests
|
||||
- BUGFIX: Fixed an issue where ngrokd's registry cache would not load from file
|
||||
- BUGFIX: Fixed an issue where ngrokd's registry cache would not save to file
|
||||
- BUGFIX: Fixed an issue where ngrok would refuse requests with an Authorization header if no HTTP auth was specified.
|
||||
- BUGFIX: Fixed a bug where ngrok would fail to cross-compile in you hadn't compiled natively first
|
||||
- IMPROVEMENT: ngrok's registry cache now handles and attempts to restore TCP URLs
|
||||
- IMPROVEMENT: Added simple Travis CI integration to make sure ngrok compiles
|
||||
|
||||
## 0.15 - 07/27/2013
|
||||
- FEATURE: ngrok can now update itself automatically
|
||||
|
||||
## 0.14 - 07/03/2013
|
||||
- BUGFIX Fix an issue where ngrok could never save/load the authtoken file on linux
|
||||
- BUGFIX Fix an issue where ngrok wouldn't emit log messages while loading authtokens
|
||||
- BUGFIX: Fix an issue where ngrok could never save/load the authtoken file on linux
|
||||
- BUGFIX: Fix an issue where ngrok wouldn't emit log messages while loading authtokens
|
||||
|
||||
## 0.13 - 07/02/2013
|
||||
- FEATURE -hostname switch on client allows you to run tunnels over custom domains (requires you CNAME your DNS)
|
||||
- IMPROVEMENT ngrok client UI now shows the client IP address for a request
|
||||
- IMPROVEMENT ngrok client UI now shows how long ago a request was made (uservoice request 4127487)
|
||||
- IMPROVEMENT ngrokd now uses and LRU cache for tunnel affinity data
|
||||
- IMPROVEMENT ngrokd can now save and restore its tunnel affinity cache to a file to preserve across restarts
|
||||
- FEATURE: -hostname switch on client allows you to run tunnels over custom domains (requires you CNAME your DNS)
|
||||
- IMPROVEMENT: ngrok client UI now shows the client IP address for a request
|
||||
- IMPROVEMENT: ngrok client UI now shows how long ago a request was made (uservoice request 4127487)
|
||||
- IMPROVEMENT: ngrokd now uses and LRU cache for tunnel affinity data
|
||||
- IMPROVEMENT: ngrokd can now save and restore its tunnel affinity cache to a file to preserve across restarts
|
||||
|
||||
## 0.12 - 06/30/2013
|
||||
- IMPROVEMENT Improved developer documentation
|
||||
- IMPROVEMENT Simplified build process with custom version of go-bindata that compiles assets into binary releases
|
||||
- BUGFIX Github issue #4: Raw/Binary requests bodies are no longer truncated at 8192 bytes.
|
||||
- IMPROVEMENT: Improved developer documentation
|
||||
- IMPROVEMENT: Simplified build process with custom version of go-bindata that compiles assets into binary releases
|
||||
- BUGFIX: Github issue #4: Raw/Binary requests bodies are no longer truncated at 8192 bytes.
|
||||
|
||||
@@ -93,7 +93,7 @@ There is a stub at _src/ngrok/main/ngrokd/ngrokd.go_ for the purposes of creatin
|
||||
|
||||
## ngrok - the client
|
||||
### Code
|
||||
Code for the server lives under src/ngrok/client
|
||||
Code for the client lives under src/ngrok/client
|
||||
|
||||
### Entry point
|
||||
The ngrok entry point is in _src/ngrok/client/main.go_.
|
||||
|
||||
9
src/ngrok/cache/lru.go
vendored
9
src/ngrok/cache/lru.go
vendored
@@ -167,17 +167,12 @@ func (lru *LRUCache) Items() []Item {
|
||||
|
||||
func (lru *LRUCache) SaveItems(w io.Writer) error {
|
||||
items := lru.Items()
|
||||
|
||||
for _, v := range items {
|
||||
gob.Register(v)
|
||||
}
|
||||
|
||||
encoder := gob.NewEncoder(w)
|
||||
return encoder.Encode(items)
|
||||
}
|
||||
|
||||
func (lru *LRUCache) SaveItemsToFile(path string) error {
|
||||
if wr, err := os.Open(path); err != nil {
|
||||
if wr, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer wr.Close()
|
||||
@@ -188,7 +183,7 @@ func (lru *LRUCache) SaveItemsToFile(path string) error {
|
||||
func (lru *LRUCache) LoadItems(r io.Reader) error {
|
||||
items := make([]Item, 0)
|
||||
decoder := gob.NewDecoder(r)
|
||||
if err := decoder.Decode(items); err != nil {
|
||||
if err := decoder.Decode(&items); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ func parseArgs() *Options {
|
||||
webport := flag.Int(
|
||||
"webport",
|
||||
4040,
|
||||
"The port on which the web interface is served")
|
||||
"The port on which the web interface is served, -1 to disable")
|
||||
|
||||
logto := flag.String(
|
||||
"log",
|
||||
|
||||
@@ -191,8 +191,8 @@ func control(s *State, ctl *ui.Controller) {
|
||||
}
|
||||
|
||||
// update UI state
|
||||
conn.Info("Tunnel established at %v", regAck.Url)
|
||||
s.publicUrl = regAck.Url
|
||||
conn.Info("Tunnel established at %v", s.GetPublicUrl())
|
||||
s.status = "online"
|
||||
s.serverVersion = regAck.MmVersion
|
||||
ctl.Update(s)
|
||||
@@ -255,7 +255,10 @@ func Main() {
|
||||
|
||||
// init ui
|
||||
ctl := ui.NewController()
|
||||
web.NewWebView(ctl, s, opts.webport)
|
||||
if opts.webport != -1 {
|
||||
web.NewWebView(ctl, s, opts.webport)
|
||||
}
|
||||
|
||||
if opts.logto != "stdout" {
|
||||
term.New(ctl, s)
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ type State struct {
|
||||
// implement client.ui.State
|
||||
func (s State) GetClientVersion() string { return version.MajorMinor() }
|
||||
func (s State) GetServerVersion() string { return s.serverVersion }
|
||||
func (s State) GetPublicUrl() string { return s.publicUrl }
|
||||
func (s State) GetLocalAddr() string { return s.opts.localaddr }
|
||||
func (s State) GetWebPort() int { return s.opts.webport }
|
||||
func (s State) GetStatus() string { return s.status }
|
||||
func (s State) GetProtocol() proto.Protocol { return s.protocol }
|
||||
func (s State) GetUpdate() ui.UpdateStatus { return s.update }
|
||||
func (s State) GetPublicUrl() string { return s.publicUrl }
|
||||
|
||||
func (s State) GetConnectionMetrics() (metrics.Meter, metrics.Timer) {
|
||||
return s.metrics.connMeter, s.metrics.connTimer
|
||||
|
||||
@@ -11,7 +11,7 @@ const (
|
||||
UpdateNone = -1 * iota
|
||||
UpdateInstalling
|
||||
UpdateReady
|
||||
UpdateError
|
||||
UpdateAvailable
|
||||
)
|
||||
|
||||
type State interface {
|
||||
|
||||
@@ -15,67 +15,100 @@ import (
|
||||
|
||||
const (
|
||||
updateEndpoint = "https://dl.ngrok.com/update"
|
||||
checkEndpoint = "https://dl.ngrok.com/update/check"
|
||||
)
|
||||
|
||||
func autoUpdate(s *State, ctl *ui.Controller, token string) {
|
||||
update := func() (updateSuccessful bool) {
|
||||
params := make(url.Values)
|
||||
params.Add("version", version.MajorMinor())
|
||||
params.Add("os", runtime.GOOS)
|
||||
params.Add("arch", runtime.GOARCH)
|
||||
|
||||
download := update.NewDownload()
|
||||
downloadComplete := make(chan int)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case progress, ok := <-download.Progress:
|
||||
if !ok {
|
||||
close(downloadComplete)
|
||||
return
|
||||
} else if progress == 100 {
|
||||
s.update = ui.UpdateInstalling
|
||||
ctl.Update(s)
|
||||
close(downloadComplete)
|
||||
return
|
||||
} else {
|
||||
if progress%25 == 0 {
|
||||
log.Info("Downloading update %d%% complete", progress)
|
||||
}
|
||||
s.update = ui.UpdateStatus(progress)
|
||||
ctl.Update(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info("Checking for update")
|
||||
err := download.UpdateFromUrl(updateEndpoint + "?" + params.Encode())
|
||||
<-downloadComplete
|
||||
if err != nil {
|
||||
log.Error("Error while updating ngrok: %v", err)
|
||||
if download.Available {
|
||||
s.update = ui.UpdateError
|
||||
func progressWatcher(s *State, ctl *ui.Controller, progress chan int, complete chan int) {
|
||||
for {
|
||||
select {
|
||||
case pct, ok := <-progress:
|
||||
if !ok {
|
||||
close(complete)
|
||||
return
|
||||
} else if pct == 100 {
|
||||
s.update = ui.UpdateInstalling
|
||||
ctl.Update(s)
|
||||
close(complete)
|
||||
return
|
||||
} else {
|
||||
s.update = ui.UpdateNone
|
||||
if pct%25 == 0 {
|
||||
log.Info("Downloading update %d%% complete", pct)
|
||||
}
|
||||
s.update = ui.UpdateStatus(pct)
|
||||
ctl.Update(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func autoUpdate(s *State, ctl *ui.Controller, token string) {
|
||||
tryAgain := true
|
||||
|
||||
params := make(url.Values)
|
||||
params.Add("version", version.MajorMinor())
|
||||
params.Add("os", runtime.GOOS)
|
||||
params.Add("arch", runtime.GOARCH)
|
||||
params.Add("user", token)
|
||||
|
||||
updateUrl := updateEndpoint + "?" + params.Encode()
|
||||
checkUrl := checkEndpoint + "?" + params.Encode()
|
||||
|
||||
update := func() {
|
||||
log.Info("Checking for update")
|
||||
available, err := update.NewDownload(checkUrl).Check()
|
||||
if err != nil {
|
||||
log.Error("Error while checking for update: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !available {
|
||||
log.Info("No update available")
|
||||
return
|
||||
}
|
||||
|
||||
// stop trying after a single download attempt
|
||||
// XXX: improve this so the we can:
|
||||
// 1. safely update multiple times
|
||||
// 2. only retry after a network connection failure
|
||||
tryAgain = false
|
||||
|
||||
download := update.NewDownload(updateUrl)
|
||||
downloadComplete := make(chan int)
|
||||
|
||||
go progressWatcher(s, ctl, download.Progress, downloadComplete)
|
||||
|
||||
log.Info("Trying to update . . .")
|
||||
err, errRecover := download.GetAndUpdate()
|
||||
<-downloadComplete
|
||||
|
||||
if err != nil {
|
||||
// log error to console
|
||||
log.Error("Error while updating ngrok: %v", err)
|
||||
|
||||
if errRecover != nil {
|
||||
log.Error("Error while recovering from failed ngrok update, your binary may be missing: %v", errRecover.Error())
|
||||
params.Add("errorRecover", errRecover.Error())
|
||||
}
|
||||
|
||||
// record the error to ngrok.com's servers for debugging purposes
|
||||
// log error to ngrok.com's servers for debugging purposes
|
||||
params.Add("error", err.Error())
|
||||
params.Add("user", token)
|
||||
resp, err := http.PostForm("https://dl.ngrok.com/update/error", params)
|
||||
resp, reportErr := http.PostForm("https://dl.ngrok.com/update/error", params)
|
||||
if err != nil {
|
||||
log.Error("Error while reporting update error")
|
||||
log.Error("Error while reporting update error: %v, %v", err, reportErr)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// tell the user to update manually
|
||||
s.update = ui.UpdateAvailable
|
||||
} else {
|
||||
if download.Available {
|
||||
log.Info("Marked update ready")
|
||||
s.update = ui.UpdateReady
|
||||
updateSuccessful = true
|
||||
if !download.Available {
|
||||
// this is the way the server tells us to update manually
|
||||
log.Info("Server wants us to update manually")
|
||||
s.update = ui.UpdateAvailable
|
||||
} else {
|
||||
log.Info("No update available at this time")
|
||||
s.update = ui.UpdateNone
|
||||
// tell the user the update is ready
|
||||
log.Info("Update ready!")
|
||||
s.update = ui.UpdateReady
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,11 +119,9 @@ func autoUpdate(s *State, ctl *ui.Controller, token string) {
|
||||
// try to update immediately and then at a set interval
|
||||
update()
|
||||
for _ = range time.Tick(updateCheckInterval) {
|
||||
if update() {
|
||||
// stop trying to update if the update function is successful
|
||||
// XXX: improve this by trying to download versions newer than
|
||||
// the last one we downloaded
|
||||
return
|
||||
if !tryAgain {
|
||||
break
|
||||
}
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (v *TermView) Render() {
|
||||
updateMsg = "ngrok is updating"
|
||||
case ui.UpdateReady:
|
||||
updateMsg = "ngrok has updated: restart ngrok for the new version"
|
||||
case ui.UpdateError:
|
||||
case ui.UpdateAvailable:
|
||||
updateMsg = "new version available at https://ngrok.com"
|
||||
default:
|
||||
pct := float64(updateStatus) / 100.0
|
||||
@@ -119,6 +119,9 @@ func (v *TermView) Render() {
|
||||
v.Printf(0, 4, "%-30s%s", "Protocol", v.state.GetProtocol().GetName())
|
||||
v.Printf(0, 5, "%-30s%s -> %s", "Forwarding", v.state.GetPublicUrl(), v.state.GetLocalAddr())
|
||||
webAddr := fmt.Sprintf("http://localhost:%d", v.state.GetWebPort())
|
||||
if v.state.GetWebPort() == -1 {
|
||||
webAddr = "disabled"
|
||||
}
|
||||
v.Printf(0, 6, "%-30s%s", "Web Interface", webAddr)
|
||||
|
||||
connMeter, connTimer := v.state.GetConnectionMetrics()
|
||||
|
||||
@@ -112,21 +112,22 @@ func makeBody(h http.Header, body []byte) SerializedBody {
|
||||
if b.RawContentType != "" {
|
||||
b.ContentType = strings.TrimSpace(strings.Split(b.RawContentType, ";")[0])
|
||||
switch b.ContentType {
|
||||
case "application/xml":
|
||||
case "text/xml":
|
||||
case "application/xml", "text/xml":
|
||||
err = xml.Unmarshal(body, new(XMLDoc))
|
||||
if err != nil {
|
||||
syntaxError := err.(*xml.SyntaxError)
|
||||
// xml syntax errors only give us a line number, so we
|
||||
// count to find an offset
|
||||
b.ErrorOffset = offsetForLine(syntaxError.Line)
|
||||
if syntaxError, ok := err.(*xml.SyntaxError); ok {
|
||||
// xml syntax errors only give us a line number, so we
|
||||
// count to find an offset
|
||||
b.ErrorOffset = offsetForLine(syntaxError.Line)
|
||||
}
|
||||
}
|
||||
|
||||
case "application/json":
|
||||
err = json.Unmarshal(body, new(json.RawMessage))
|
||||
if err != nil {
|
||||
syntaxError := err.(*json.SyntaxError)
|
||||
b.ErrorOffset = int(syntaxError.Offset)
|
||||
if syntaxError, ok := err.(*json.SyntaxError); ok {
|
||||
b.ErrorOffset = int(syntaxError.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
case "application/x-www-form-urlencoded":
|
||||
@@ -229,7 +230,7 @@ func (h *WebHttpView) register() {
|
||||
r.ParseForm()
|
||||
txnid := r.Form.Get("txnid")
|
||||
if txn, ok := h.idToTxn[txnid]; ok {
|
||||
bodyBytes, err := httputil.DumpRequestOut(txn.HttpTxn.Req.Request, true)
|
||||
bodyBytes, err := httputil.DumpRequest(txn.HttpTxn.Req.Request, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -37,8 +37,14 @@ type PrefixLogger struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
func NewPrefixLogger() Logger {
|
||||
return &PrefixLogger{Logger: &root}
|
||||
func NewPrefixLogger(prefixes ...string) Logger {
|
||||
logger := &PrefixLogger{Logger: &root}
|
||||
|
||||
for _, p := range prefixes {
|
||||
logger.AddLogPrefix(p)
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) pfx(fmtstr string) interface{} {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net/http/httputil"
|
||||
"ngrok/conn"
|
||||
"ngrok/util"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -111,5 +112,29 @@ func (h *Http) readResponses(tee *conn.Tee, lastTxn chan *HttpTxn) {
|
||||
}
|
||||
|
||||
h.Txns.In() <- txn
|
||||
|
||||
// XXX: remove web socket shim in favor of a real websocket protocol analyzer
|
||||
if txn.Req.Header.Get("Upgrade") == "websocket" {
|
||||
tee.Info("Upgrading to websocket")
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// shim for websockets
|
||||
// in order for websockets to work, we need to continue reading all of the
|
||||
// the bytes in the analyzer so that the joined connections will continue
|
||||
// sending bytes to each other
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
ioutil.ReadAll(tee.WriteBuffer())
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
ioutil.ReadAll(tee.ReadBuffer())
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"ngrok/conn"
|
||||
"ngrok/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -67,20 +68,23 @@ func httpHandler(tcpConn net.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
// read out the Host header from the request
|
||||
host := strings.ToLower(req.Host)
|
||||
conn.Debug("Found hostname %s in request", host)
|
||||
|
||||
// multiplex to find the right backend host
|
||||
conn.Debug("Found hostname %s in request", req.Host)
|
||||
tunnel := tunnels.Get("http://" + req.Host)
|
||||
tunnel := tunnels.Get("http://" + host)
|
||||
if tunnel == nil {
|
||||
conn.Info("No tunnel found for hostname %s", req.Host)
|
||||
conn.Write([]byte(fmt.Sprintf(NotFound, len(req.Host)+18, req.Host)))
|
||||
conn.Info("No tunnel found for hostname %s", host)
|
||||
conn.Write([]byte(fmt.Sprintf(NotFound, len(host)+18, host)))
|
||||
return
|
||||
}
|
||||
|
||||
// satisfy auth, if necessary
|
||||
conn.Debug("From client: %s", req.Header.Get("Authorization"))
|
||||
conn.Debug("To match: %s", tunnel.regMsg.HttpAuth)
|
||||
if req.Header.Get("Authorization") != tunnel.regMsg.HttpAuth {
|
||||
conn.Info("Authentication failed")
|
||||
// If the client specified http auth and it doesn't match this request's auth
|
||||
// then fail the request with 401 Not Authorized and request the client reissue the
|
||||
// request with basic authdeny the request
|
||||
if tunnel.regMsg.HttpAuth != "" && req.Header.Get("Authorization") != tunnel.regMsg.HttpAuth {
|
||||
conn.Info("Authentication failed: %s", req.Header.Get("Authorization"))
|
||||
conn.Write([]byte(NotAuthorized))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ var (
|
||||
tunnels *TunnelRegistry
|
||||
registryCacheSize uint64 = 1024 * 1024 // 1 MB
|
||||
domain string
|
||||
publicPort int
|
||||
)
|
||||
|
||||
func parseArgs() *Options {
|
||||
@@ -108,6 +109,7 @@ func Main() {
|
||||
// parse options
|
||||
opts := parseArgs()
|
||||
domain = opts.domain
|
||||
publicPort = opts.publicPort
|
||||
|
||||
// init logging
|
||||
log.LogTo(opts.logto)
|
||||
|
||||
@@ -23,8 +23,6 @@ func init() {
|
||||
} else {
|
||||
metrics = NewLocalMetrics(30 * time.Second)
|
||||
}
|
||||
|
||||
metrics.AddLogPrefix("metrics")
|
||||
}
|
||||
|
||||
type Metrics interface {
|
||||
@@ -63,7 +61,7 @@ type LocalMetrics struct {
|
||||
|
||||
func NewLocalMetrics(reportInterval time.Duration) *LocalMetrics {
|
||||
metrics := LocalMetrics{
|
||||
Logger: log.NewPrefixLogger(),
|
||||
Logger: log.NewPrefixLogger("metrics"),
|
||||
reportInterval: reportInterval,
|
||||
windowsCounter: gometrics.NewCounter(),
|
||||
linuxCounter: gometrics.NewCounter(),
|
||||
@@ -171,7 +169,7 @@ type KeenIoMetrics struct {
|
||||
|
||||
func NewKeenIoMetrics() *KeenIoMetrics {
|
||||
k := &KeenIoMetrics{
|
||||
Logger: log.NewPrefixLogger(),
|
||||
Logger: log.NewPrefixLogger("metrics"),
|
||||
ApiKey: os.Getenv("KEEN_API_KEY"),
|
||||
ProjectToken: os.Getenv("KEEN_PROJECT_TOKEN"),
|
||||
Requests: make(chan *KeenIoRequest, 100),
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net"
|
||||
"ngrok/cache"
|
||||
"ngrok/log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -22,32 +24,54 @@ func (url cacheUrl) Size() int {
|
||||
type TunnelRegistry struct {
|
||||
tunnels map[string]*Tunnel
|
||||
affinity *cache.LRUCache
|
||||
log.Logger
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewTunnelRegistry(cacheSize uint64, cacheFile string) *TunnelRegistry {
|
||||
manager := &TunnelRegistry{
|
||||
registry := &TunnelRegistry{
|
||||
tunnels: make(map[string]*Tunnel),
|
||||
affinity: cache.NewLRUCache(cacheSize),
|
||||
Logger: log.NewPrefixLogger("registry"),
|
||||
}
|
||||
|
||||
// LRUCache uses Gob encoding. Unfortunately, Gob is fickle and will fail
|
||||
// to encode or decode any non-primitive types that haven't been "registered"
|
||||
// with it. Since we store cacheUrl objects, we need to register them here first
|
||||
// for the encoding/decoding to work
|
||||
var urlobj cacheUrl
|
||||
gob.Register(urlobj)
|
||||
|
||||
if cacheFile != "" {
|
||||
// load cache entries from file
|
||||
manager.affinity.LoadItemsFromFile(cacheFile)
|
||||
err := registry.affinity.LoadItemsFromFile(cacheFile)
|
||||
if err != nil {
|
||||
registry.Error("Failed to load affinity cache %s: %v", cacheFile, err)
|
||||
}
|
||||
|
||||
// save cache periodically to file
|
||||
manager.SaveCacheThread(cacheFile, cacheSaveInterval)
|
||||
registry.SaveCacheThread(cacheFile, cacheSaveInterval)
|
||||
} else {
|
||||
registry.Info("No affinity cache specified")
|
||||
}
|
||||
|
||||
return manager
|
||||
return registry
|
||||
}
|
||||
|
||||
// Spawns a goroutine the periodically saves the cache to a file.
|
||||
func (r *TunnelRegistry) SaveCacheThread(path string, interval time.Duration) {
|
||||
go func() {
|
||||
r.Info("Saving affinity cache to %s every %s", path, interval.String())
|
||||
for {
|
||||
time.Sleep(interval)
|
||||
r.affinity.SaveItemsToFile(path)
|
||||
|
||||
r.Debug("Saving affinity cache")
|
||||
err := r.affinity.SaveItemsToFile(path)
|
||||
if err != nil {
|
||||
r.Error("Failed to save affinity cache: %v", err)
|
||||
} else {
|
||||
r.Info("Saved affinity cache")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -67,17 +91,17 @@ func (r *TunnelRegistry) Register(url string, t *Tunnel) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register a tunnel with the following process:
|
||||
// Consult the affinity cache to try to assign a previously used tunnel url if possible
|
||||
// Generate new urls repeatedly with the urlFn and register until one is available.
|
||||
func (r *TunnelRegistry) RegisterRepeat(urlFn func() string, t *Tunnel) string {
|
||||
var url string
|
||||
|
||||
func (r *TunnelRegistry) cacheKeys(t *Tunnel) (ip string, id string) {
|
||||
clientIp := t.ctl.conn.RemoteAddr().(*net.TCPAddr).IP.String()
|
||||
clientId := t.regMsg.ClientId
|
||||
|
||||
ipCacheKey := fmt.Sprintf("client-ip:%s", clientIp)
|
||||
idCacheKey := fmt.Sprintf("client-id:%s", clientId)
|
||||
ipKey := fmt.Sprintf("client-ip-%s:%s", t.regMsg.Protocol, clientIp)
|
||||
idKey := fmt.Sprintf("client-id-%s:%s", t.regMsg.Protocol, clientId)
|
||||
return ipKey, idKey
|
||||
}
|
||||
|
||||
func (r *TunnelRegistry) GetCachedRegistration(t *Tunnel) (url string) {
|
||||
ipCacheKey, idCacheKey := r.cacheKeys(t)
|
||||
|
||||
// check cache for ID first, because we prefer that over IP which might
|
||||
// not be specific to a user because of NATs
|
||||
@@ -87,24 +111,42 @@ func (r *TunnelRegistry) RegisterRepeat(urlFn func() string, t *Tunnel) string {
|
||||
} else if v, ok := r.affinity.Get(ipCacheKey); ok {
|
||||
url = string(v.(cacheUrl))
|
||||
t.Debug("Found registry affinity %s for %s", url, ipCacheKey)
|
||||
} else {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *TunnelRegistry) RegisterAndCache(url string, t *Tunnel) (err error) {
|
||||
if err = r.Register(url, t); err == nil {
|
||||
// we successfully assigned a url, cache it
|
||||
ipCacheKey, idCacheKey := r.cacheKeys(t)
|
||||
r.affinity.Set(ipCacheKey, cacheUrl(url))
|
||||
r.affinity.Set(idCacheKey, cacheUrl(url))
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// Register a tunnel with the following process:
|
||||
// Consult the affinity cache to try to assign a previously used tunnel url if possible
|
||||
// Generate new urls repeatedly with the urlFn and register until one is available.
|
||||
func (r *TunnelRegistry) RegisterRepeat(urlFn func() string, t *Tunnel) (string, error) {
|
||||
url := r.GetCachedRegistration(t)
|
||||
if url == "" {
|
||||
url = urlFn()
|
||||
}
|
||||
|
||||
for {
|
||||
if err := r.Register(url, t); err != nil {
|
||||
maxAttempts := 5
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
if err := r.RegisterAndCache(url, t); err != nil {
|
||||
// pick a new url and try again
|
||||
url = urlFn()
|
||||
} else {
|
||||
// we successfully assigned a url, we're done
|
||||
|
||||
// save our choice in the cache
|
||||
r.affinity.Set(ipCacheKey, cacheUrl(url))
|
||||
r.affinity.Set(idCacheKey, cacheUrl(url))
|
||||
|
||||
return url
|
||||
return url, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Failed to assign a URL after %d attempts!", maxAttempts)
|
||||
}
|
||||
|
||||
func (r *TunnelRegistry) Del(url string) {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"ngrok/log"
|
||||
"ngrok/msg"
|
||||
"ngrok/version"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -56,47 +58,89 @@ func newTunnel(m *msg.RegMsg, ctl *Control) (t *Tunnel) {
|
||||
t.ctl.stop <- &msg.RegAckMsg{Error: err.Error()}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
switch t.regMsg.Protocol {
|
||||
case "tcp":
|
||||
var err error
|
||||
t.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 0})
|
||||
var port int = 0
|
||||
|
||||
if err != nil {
|
||||
t.ctl.conn.Error("Failed to create tunnel. Error binding TCP listener: %v", err)
|
||||
|
||||
t.ctl.stop <- &msg.RegAckMsg{Error: "Internal server error"}
|
||||
// try to return to you the same port you had before
|
||||
cachedUrl := tunnels.GetCachedRegistration(t)
|
||||
if cachedUrl != "" {
|
||||
parts := strings.Split(cachedUrl, ":")
|
||||
portPart := parts[len(parts)-1]
|
||||
port, err = strconv.Atoi(portPart)
|
||||
if err != nil {
|
||||
t.ctl.conn.Error("Failed to parse cached url port as integer: %s", portPart)
|
||||
// continue with zero
|
||||
port = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Bind for TCP connections
|
||||
t.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: port})
|
||||
|
||||
// If we failed with a custom port, try with a random one
|
||||
if err != nil && port != 0 {
|
||||
t.ctl.conn.Warn("Failed to get custom port %d: %v, trying a random one", port, err)
|
||||
t.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 0})
|
||||
}
|
||||
|
||||
// we tried to bind with a random port and failed (no more ports available?)
|
||||
if err != nil {
|
||||
failReg(t.ctl.conn.Error("Error binding TCP listener: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// create the url
|
||||
addr := t.listener.Addr().(*net.TCPAddr)
|
||||
t.url = fmt.Sprintf("tcp://%s:%d", domain, addr.Port)
|
||||
|
||||
if err = tunnels.Register(t.url, t); err != nil {
|
||||
// This should never be possible because the OS will only assign
|
||||
// available ports to us.
|
||||
t.Error("TCP listener bound, but failed to register: %s", err.Error())
|
||||
// register it
|
||||
if err = tunnels.RegisterAndCache(t.url, t); err != nil {
|
||||
// This should never be possible because the OS will
|
||||
// only assign available ports to us.
|
||||
t.listener.Close()
|
||||
failReg(err)
|
||||
failReg(fmt.Errorf("TCP listener bound, but failed to register %s", t.url))
|
||||
return
|
||||
}
|
||||
|
||||
go t.listenTcp(t.listener)
|
||||
|
||||
case "http":
|
||||
vhost := os.Getenv("VHOST")
|
||||
if vhost == "" {
|
||||
vhost = fmt.Sprintf("%s:%d", domain, publicPort)
|
||||
}
|
||||
|
||||
// Canonicalize virtual host on default port 80
|
||||
if strings.HasSuffix(vhost, ":80") {
|
||||
vhost = vhost[0 : len(vhost)-3]
|
||||
}
|
||||
|
||||
if strings.TrimSpace(t.regMsg.Hostname) != "" {
|
||||
t.url = fmt.Sprintf("http://%s", t.regMsg.Hostname)
|
||||
} else if strings.TrimSpace(t.regMsg.Subdomain) != "" {
|
||||
t.url = fmt.Sprintf("http://%s.%s", t.regMsg.Subdomain, domain)
|
||||
t.url = fmt.Sprintf("http://%s.%s", t.regMsg.Subdomain, vhost)
|
||||
}
|
||||
|
||||
vhost = strings.ToLower(vhost)
|
||||
t.url = strings.ToLower(t.url)
|
||||
|
||||
if t.url != "" {
|
||||
if err := tunnels.Register(t.url, t); err != nil {
|
||||
failReg(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
t.url = tunnels.RegisterRepeat(func() string {
|
||||
return fmt.Sprintf("http://%x.%s", rand.Int31(), domain)
|
||||
t.url, err = tunnels.RegisterRepeat(func() string {
|
||||
return fmt.Sprintf("http://%x.%s", rand.Int31(), vhost)
|
||||
}, t)
|
||||
|
||||
if err != nil {
|
||||
failReg(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
const (
|
||||
Proto = "1"
|
||||
Major = "0"
|
||||
Minor = "15"
|
||||
Minor = "22"
|
||||
)
|
||||
|
||||
func MajorMinor() string {
|
||||
|
||||
Reference in New Issue
Block a user