163

I'm making a web app that requires that I check to see if remote servers are online or not. When I run it from the command line, my page load goes up to a full 60s (for 8 entries, it will scale linearly with more).

I decided to go the route of pinging on the user's end. This way, I can load the page and just have them wait for the "server is online" data while browsing my content.

If anyone has the answer to the above question, or if they know a solution to keep my page loads fast, I'd definitely appreciate it.

Grant Thomas
  • 42,191
  • 8
  • 81
  • 120
Chuck Callebs
  • 15,625
  • 8
  • 53
  • 71

17 Answers17

144

I have found someone that accomplishes this with a very clever usage of the native Image object.

From their source, this is the main function (it has dependences on other parts of the source but you get the idea).

function Pinger_ping(ip, callback) {

  if(!this.inUse) {

    this.inUse = true;
    this.callback = callback
    this.ip = ip;

    var _that = this;

    this.img = new Image();

    this.img.onload = function() {_that.good();};
    this.img.onerror = function() {_that.good();};

    this.start = new Date().getTime();
    this.img.src = "http://" + ip;
    this.timer = setTimeout(function() { _that.bad();}, 1500);

  }
}

This works on all types of servers that I've tested (web servers, ftp servers, and game servers). It also works with ports. If anyone encounters a use case that fails, please post in the comments and I will update my answer.

Update: Previous link has been removed. If anyone finds or implements the above, please comment and I'll add it into the answer.

Update 2: @trante was nice enough to provide a jsFiddle.

http://jsfiddle.net/GSSCD/203/

Update 3: @Jonathon created a GitHub repo with the implementation.

https://github.com/jdfreder/pingjs

Update 4: It looks as if this implementation is no longer reliable. People are also reporting that Chrome no longer supports it all, throwing a net::ERR_NAME_NOT_RESOLVED error. If someone can verify an alternate solution I will put that as the accepted answer.

Chuck Callebs
  • 15,625
  • 8
  • 53
  • 71
  • 51
    This is what I've been using. It does have one flaw, however, and that is that the "image" is cached. When I ping a given IP initially, I get 304 ms - but if I ping it a second time without a page reload, I get 2 ms instead. This could be avoided by appending a `"/?cachebreaker="+new Date().getTime();` to the end of the img src if necessary. – Meshaal Aug 14 '12 at 08:06
  • Good catch. I actually did notice that, but just assumed it was unavoidable. Adding any slug to the end should definitely fix it though. – Chuck Callebs Aug 14 '12 at 14:51
  • Another example using the Image approach is described [here](http://bytes.com/topic/javascript/answers/93208-using-javascript-determine-if-web-server-responding) – ChristianSchaefer Feb 20 '13 at 15:20
  • 2
    this is not working for me, a known bad hostname results in Ping request could not find host `...`. but since onerror is 'good' this thing says it responded – Maslow Mar 04 '13 at 19:24
  • The `JS` result and simple `ping` in cmd.exe are way different. Due to the protocol difference, but which is more reliable? – ShouravBR Apr 09 '13 at 15:11
  • Can I run this in a html page and Can i run this in node.js command? – Johnny Chen May 09 '14 at 13:32
  • Is there a way to retrieve the url's IP address using this technique? – chris May 13 '14 at 14:11
  • 1
    Just as a sidenote, if your able to use an actual image as .src - Then that is, in my opinion better. Just because this way of pinging, will always return an error, and relys on a connection timeout for it to say its offline. If the server has for some reason crashed or is "unavailable" it will still return OK as long as it has some kind of response (503, 500 e.e). – Christer Aug 25 '14 at 14:34
  • I don't got it... How do I get the delay value using this method? – Guill Nov 06 '14 at 12:07
  • that jsfiddle example seems a little confusing thou, with the knockout.js – Alberici Nov 21 '14 at 15:46
  • another thing, is there any possible way to clean the cache? if you can connect and suddenly you can, it will still say that the Server Responded – Alberici Nov 21 '14 at 17:44
  • So this is a way to sort out CORS problems from server down problems. Could we please add a list of browsers where this works? I just tried Chrome (on Windows 7) where it works. – Leo Jan 03 '15 at 22:31
  • 5
    More testing revealed that this is totally unreliable. – Leo Jan 09 '15 at 14:00
  • @Leo Could you offer more specifics? – Chuck Callebs Jan 13 '16 at 15:11
  • 1
    It looks like this isn't working anymore, I can't catch the error in Chrome so everything is responding, in my case the `net::ERR_NAME_NOT_RESOLVED` error. – Roy Jan 20 '16 at 15:30
  • 2
    The Ping API that @Jonathon created will successfully ping everything. Sites that do not exist and random characters. – IE5Master Jul 25 '16 at 15:31
  • 2
    The Ping API actually always fails with onerror. HOWEVER if the target URL denotes an image, it fires onload which is awesome! Bypasses CORS checks. – Martin Vysny Feb 02 '17 at 08:03
  • Is the `Update 2` jsfiddle still unreliable? Do we have an updated one that is reliable now? This is a beautiful solution by the way, just wished it were reliable. – Lasagna Cat Apr 17 '17 at 18:25
  • 1
    I updated the fiddle to alert when a server comes back online (in case you're waiting for a fix, etc). And it does seem to work just fine for timeouts. Fiddle: http://jsfiddle.net/Seabiscuit/u7nogfce/6/ – seebiscuit Jul 06 '17 at 14:50
  • Still unreliable, I'm getting successes everytime. @Meshaal your solution seems ineffective, I placed it here: `img.src = url + '?random-no-cache=' + Math.floor((1 + Math.random()) * 0x10000).toString(16) + '&cachebreaker=' + new Date().getTime();` within the `request_image(url)` of [ping.js](https://github.com/jdfreder/pingjs) – russellhoff Jan 24 '18 at 08:16
  • 1
    @russellhoff I'm not sure what about my solution was found to be ineffective, it does what it's meant to (avoiding caching)—it's just that the solution as a whole doesn't work anymore. Or at least, not universally: all of this seems to still be working for me in Chrome, but not in Safari. – Meshaal Jan 24 '18 at 22:38
  • @Meshaal you're right. But the entire thing doesn't seem reliable. – russellhoff Jan 25 '18 at 14:46
  • Is this solution multi browser? Anyone tested it like that? – Gabriel Nov 26 '19 at 21:28
23

Ping is ICMP, but if there is any open TCP port on the remote server it could be achieved like this:

function ping(host, port, pong) {

  var started = new Date().getTime();

  var http = new XMLHttpRequest();

  http.open("GET", "http://" + host + ":" + port, /*async*/true);
  http.onreadystatechange = function() {
    if (http.readyState == 4) {
      var ended = new Date().getTime();

      var milliseconds = ended - started;

      if (pong != null) {
        pong(milliseconds);
      }
    }
  };
  try {
    http.send(null);
  } catch(exception) {
    // this is expected
  }

}

Nils Holgersson
  • 231
  • 2
  • 2
  • I like this tricky solution. Having to provide a function for pong instead of returning a number is a little odd to me but workable. – Nick Feb 14 '13 at 02:58
  • @armen: you have to provide a function as third argument to ping(), the value of this argument is called pong within the function. – finitud Aug 12 '13 at 13:38
  • 2
    `ping("example.com", "77", function(m){ console.log("It took "+m+" miliseconds."); })` .....example call – jave.web Oct 18 '15 at 13:06
  • @Nick: This is asynchronous stuff, so at the time the method returns, the `onreadystatechange` has not fired yet. This means you need a callback to pick up when the state changes. – awe Dec 22 '16 at 08:54
  • Whatever I choose for host, pong() is called. ping(`test.zzzzzzzzz`, "77", function(m){ console.log("It took "+m+" miliseconds."); }) prints “It took 67 miliseconds.” ping(`stackoverflow.com`, "80", function(m){ console.log("It took "+m+" miliseconds."); }) gives a CORS-Error: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSMissingAllowOrigin I do not see how I can check with this code if a remote computer is online. – Tux Dec 13 '19 at 08:54
  • This does not handle `https` – Dimitri Kopriwa Aug 21 '20 at 15:08
  • It takes 140 Seconds to get a response, if the server is unavailable – Contrean Feb 18 '21 at 15:22
18

you can try this:

put ping.html on the server with or without any content, on the javascript do same as below:

<script>
    function ping(){
       $.ajax({
          url: 'ping.html',
          success: function(result){
             alert('reply');
          },     
          error: function(result){
              alert('timeout/error');
          }
       });
    }
</script>
jerjer
  • 8,482
  • 27
  • 36
  • 1
    The servers I'm pinging aren't my own, so I don't have that option. Thanks though. – Chuck Callebs Nov 26 '10 at 02:53
  • 9
    @dlchambers Not Dues to CORS but DUE TO cross domain poilicy. CORS allows a server to specify the origins plus it has to be supported by the browser. so it is more like a cross domain issue. – Royi Namir Apr 10 '13 at 07:59
  • The HEAD type requests won't work because of CORS either. Tested with Chromium 55. Receiving the same error with http code 0 as in the case of net::CONNECTION_REFUSED for example. – Martin Vysny Feb 02 '17 at 06:45
  • This is more cross browser firendly. – Gabriel Dec 05 '19 at 19:56
14

You can't directly "ping" in javascript. There may be a few other ways:

  • Ajax
  • Using a java applet with isReachable
  • Writing a serverside script which pings and using AJAX to communicate to your serversidescript
  • You might also be able to ping in flash (actionscript)
Eugene
  • 9,015
  • 18
  • 58
  • 86
  • 2
    Java's isReachable is quite unreliable.... But well it's 2018 and Java Applets are outdated anyway. – dreua Feb 17 '18 at 22:02
8

You can't do regular ping in browser Javascript, but you can find out if remote server is alive by for example loading an image from the remote server. If loading fails -> server down.

You can even calculate the loading time by using onload-event. Here's an example how to use onload event.

Community
  • 1
  • 1
Epeli
  • 16,564
  • 10
  • 63
  • 76
  • 4
    The unfortunate thing about that is the servers I'm pinging aren't web servers. They're game servers. – Chuck Callebs Nov 26 '10 at 02:43
  • 1
    Then you have to do the pinging on the server side - or you could consider some lightweight http-server. – Epeli Nov 26 '10 at 02:45
  • Do you know of a way to do aggregate pings? That's my main slowdown, waiting for one request to finish before the other begins. – Chuck Callebs Nov 26 '10 at 02:52
  • You need to ping the servers concurrently. You can achieve that with select, threads or processes. For really hardcore solution you could use Eventmachine. – Epeli Nov 26 '10 at 02:59
  • Thanks a lot, this is what I'll do. – Chuck Callebs Nov 26 '10 at 03:09
  • 1
    You might look into taking advantage of the command-line `ping` command. It's industrial strength. Or, there are all sorts of free/open-source apps to monitor if a host is running, using various types of heartbeat checks. – the Tin Man Nov 26 '10 at 03:54
7

Pitching in with a websocket solution...

function ping(ip, isUp, isDown) {
  var ws = new WebSocket("ws://" + ip);
  ws.onerror = function(e){
    isUp();
    ws = null;
  };
  setTimeout(function() { 
    if(ws != null) {
      ws.close();
      ws = null;
      isDown();
    }
  },2000);
}
Antony Woods
  • 4,060
  • 2
  • 22
  • 46
  • 1
    Shouldn't the `isUp();` call be in `onopen` event handler? :) – jave.web Oct 18 '15 at 12:57
  • 1
    It uses websocket protocol but *assumes* there isn't actually a websocket server waiting for a connection. This is just for pinging 'any old IP'. – Antony Woods Oct 18 '15 at 19:12
  • But how can you detect whether WebSocket is just not supported or there really was some error ? :) – jave.web Oct 19 '15 at 03:58
  • If think you'll be unlucky enough to ping an IP that *could* accept a websocket connection on port 80, then yes, you should **also** add `isUp();` to that callback. Or mitigate that by adding a distinctly non-websocket-y port. – Antony Woods Oct 19 '15 at 12:03
  • I voted this up because I came here looking for a solution to what happens when my websocket server gets rebooted, and this answer showed me I could setTimeout in the error handler of the socket and attempt to reconnect (busy wait). Probably not best practice, but solves my issue. :) – coolhandle01 Dec 16 '16 at 14:13
  • Am I right with my assumption that `onerror` behaviour has changed? I don't use IPs but url, but this gives me `xy is up` for any url I provide, because I get: `Firefox can't establish a connection to the server at ws://...` even if that url is completely made up –  Mar 09 '17 at 21:11
  • Note that for HTTPS, you'll have to use `wss://` instead. I'm still looking for a way to do `ws://` over HTTP for pinging to local networked devices that do not support wss. – Tabbernaut Oct 25 '17 at 10:26
6

To keep your requests fast, cache the server side results of the ping and update the ping file or database every couple of minutes(or however accurate you want it to be). You can use cron to run a shell command with your 8 pings and write the output into a file, the webserver will include this file into your view.

user456733
  • 341
  • 1
  • 3
  • I think this is the most reliable way with best possible for accurate result. However since it still requires some works on server I would say it is not smart way. – Chetabahana Mar 24 '15 at 14:36
5

If what you are trying to see is whether the server "exists", you can use the following:

function isValidURL(url) {
    var encodedURL = encodeURIComponent(url);
    var isValid = false;

    $.ajax({
      url: "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22" + encodedURL + "%22&format=json",
      type: "get",
      async: false,
      dataType: "json",
      success: function(data) {
        isValid = data.query.results != null;
      },
      error: function(){
        isValid = false;
      }
    });

    return isValid;
}

This will return a true/false indication whether the server exists.

If you want response time, a slight modification will do:

function ping(url) {
    var encodedURL = encodeURIComponent(url);
    var startDate = new Date();
    var endDate = null;
    $.ajax({
      url: "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22" + encodedURL + "%22&format=json",
      type: "get",
      async: false,
      dataType: "json",
      success: function(data) {
        if (data.query.results != null) {
            endDate = new Date();
        } else {
            endDate = null;
        }
      },
      error: function(){
        endDate = null;
      }
    });

    if (endDate == null) {
        throw "Not responsive...";
    }

    return endDate.getTime() - startDate.getTime();
}

The usage is then trivial:

var isValid = isValidURL("http://example.com");
alert(isValid ? "Valid URL!!!" : "Damn...");

Or:

var responseInMillis = ping("example.com");
alert(responseInMillis);
YasirA
  • 9,225
  • 2
  • 36
  • 60
Ohad
  • 204
  • 2
  • 14
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – vonbrand May 07 '14 at 21:48
  • 1
    this is a great solution, but unfortunately, the yahoo domain query service seems to give variable results. for example if you try the following URLs, begin-download .com beginfreedownload .com they come back as not registered, but if you go to those URLs, they clearly are registered. any idea whats going on here? (i've added a space to the domain names in these examples, just to avoid giving out google juice. in my test i didnt have a space in them) – user280109 Jan 12 '16 at 03:59
  • You are doing a request to Yahoo! API, ping won't be even the same if you do a request from your machine. This makes no sense – Andre Figueiredo Mar 22 '16 at 20:10
  • This causes `No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin` and success callback is never called – vladkras Feb 15 '18 at 16:30
4

There are many crazy answers here and especially about CORS -

You could do an http HEAD request (like GET but without payload). See https://ochronus.com/http-head-request-good-uses/

It does NOT need a preflight check, the confusion is because of an old version of the specification, see Why does a cross-origin HEAD request need a preflight check?

So you could use the answer above which is using the jQuery library (didn't say it) but with

type: 'HEAD'

--->

<script>
    function ping(){
       $.ajax({
          url: 'ping.html',
          type: 'HEAD',
          success: function(result){
             alert('reply');
          },     
          error: function(result){
              alert('timeout/error');
          }
       });
    }
</script>

Off course you can also use vanilla js or dojo or whatever ...

Community
  • 1
  • 1
sebilasse
  • 3,202
  • 2
  • 30
  • 30
  • 1
    Sending HEAD request still fails with "XMLHttpRequest cannot load IP. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'ip2' is therefore not allowed access." So blocked by CORS. You will receive success with http status 0, and you will thus be unable to differentiate this from net::ERR_CONNECTION_REFUSED for example – Martin Vysny Feb 01 '17 at 17:29
4

The problem with standard pings is they're ICMP, which a lot of places don't let through for security and traffic reasons. That might explain the failure.

Ruby prior to 1.9 had a TCP-based ping.rb, which will run with Ruby 1.9+. All you have to do is copy it from the 1.8.7 installation to somewhere else. I just confirmed that it would run by pinging my home router.

the Tin Man
  • 150,910
  • 39
  • 198
  • 279
1
const ping = (url, timeout = 6000) => {
  return new Promise((resolve, reject) => {
    const urlRule = new RegExp('(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]');
    if (!urlRule.test(url)) reject('invalid url');
    try {
      fetch(url)
        .then(() => resolve(true))
        .catch(() => resolve(false));
      setTimeout(() => {
        resolve(false);
      }, timeout);
    } catch (e) {
      reject(e);
    }
  });
};

use like this:

ping('https://stackoverflow.com/')
  .then(res=>console.log(res))
  .catch(e=>console.log(e))
Aamir
  • 5,071
  • 2
  • 27
  • 45
王玉略
  • 847
  • 6
  • 7
0

I don't know what version of Ruby you're running, but have you tried implementing ping for ruby instead of javascript? http://raa.ruby-lang.org/project/net-ping/

wajiw
  • 11,780
  • 17
  • 50
  • 73
  • Yeah, I've tried that but the pings always came back false, regardless of the web server. I changed it to just use `ping server.com` syntax. – Chuck Callebs Nov 26 '10 at 02:47
-1
let webSite = 'https://google.com/' 
https.get(webSite, function (res) {
    // If you get here, you have a response.
    // If you want, you can check the status code here to verify that it's `200` or some other `2xx`.
    console.log(webSite + ' ' + res.statusCode)
}).on('error', function(e) {
    // Here, an error occurred.  Check `e` for the error.
    console.log(e.code)
});;

if you run this with node it would console log 200 as long as google is not down.

-3

You can run the DOS ping.exe command from javaScript using the folowing:

function ping(ip)
{
    var input = "";
    var WshShell = new ActiveXObject("WScript.Shell");
    var oExec = WshShell.Exec("c:/windows/system32/ping.exe " + ip);

    while (!oExec.StdOut.AtEndOfStream)
    {
            input += oExec.StdOut.ReadLine() + "<br />";
    }
    return input;
}

Is this what was asked for, or am i missing something?

garryg
  • 35
  • 1
  • 4
    This will only work on IE unfortunately and only on a Windows Server. A better option would be to use AJAX to run a server side script. – Andrew Grothe Sep 18 '13 at 13:03
  • What the heck? Active X objects can exec in the user's file system (in IE on win server)? I hadn't worked with ActiveX but didn't imagine that... – Julix Dec 18 '19 at 23:06
-6

just replace

file_get_contents

with

$ip = $_SERVER['xxx.xxx.xxx.xxx'];
exec("ping -n 4 $ip 2>&1", $output, $retval);
if ($retval != 0) { 
  echo "no!"; 
} 
else{ 
  echo "yes!"; 
}
  • 1
    You should edit your [previous answer](http://stackoverflow.com/a/9840137/694852) instead of adding new 'partial' one. – Artemix Dec 23 '12 at 05:39
-8

It might be a lot easier than all that. If you want your page to load then check on the availability or content of some foreign page to trigger other web page activity, you could do it using only javascript and php like this.

yourpage.php

<?php
if (isset($_GET['urlget'])){
  if ($_GET['urlget']!=''){
    $foreignpage= file_get_contents('http://www.foreignpage.html');
    // you could also use curl for more fancy internet queries or if http wrappers aren't active in your php.ini
    // parse $foreignpage for data that indicates your page should proceed
    echo $foreignpage; // or a portion of it as you parsed
    exit();  // this is very important  otherwise you'll get the contents of your own page returned back to you on each call
  }
}
?>

<html>
  mypage html content
  ...

<script>
var stopmelater= setInterval("getforeignurl('?urlget=doesntmatter')", 2000);

function getforeignurl(url){
  var handle= browserspec();
  handle.open('GET', url, false);
  handle.send();
  var returnedPageContents= handle.responseText;
  // parse page contents for what your looking and trigger javascript events accordingly.
  // use handle.open('GET', url, true) to allow javascript to continue executing. must provide a callback function to accept the page contents with handle.onreadystatechange()
}
function browserspec(){
  if (window.XMLHttpRequest){
    return new XMLHttpRequest();
  }else{
    return new ActiveXObject("Microsoft.XMLHTTP");
  }
}

</script>

That should do it.

The triggered javascript should include clearInterval(stopmelater)

Let me know if that works for you

Jerry

-13

You could try using PHP in your web page...something like this:

<html><body>
<form method="post" name="pingform" action="<?php echo $_SERVER['PHP_SELF']; ?>">
<h1>Host to ping:</h1>
<input type="text" name="tgt_host" value='<?php echo $_POST['tgt_host']; ?>'><br>
<input type="submit" name="submit" value="Submit" >
</form></body>
</html>
<?php

$tgt_host = $_POST['tgt_host'];
$output = shell_exec('ping -c 10 '. $tgt_host.');

echo "<html><body style=\"background-color:#0080c0\">
<script type=\"text/javascript\" language=\"javascript\">alert(\"Ping Results: " . $output . ".\");</script>
</body></html>";

?>

This is not tested so it may have typos etc...but I am confident it would work. Could be improved too...

Chris
  • 1,603
  • 6
  • 32
  • 50
  • 6
    According to the OP's tags this is actually a Ruby on Rails question, so using PHP isn't a good solution. – the Tin Man Nov 26 '10 at 07:05
  • 1
    Not to start an argument here but isn't the point of this forum to help? – Chris Nov 26 '10 at 17:40
  • 6
    @Chris It is, but what kind of help did you give to OP who uses Ruby and JS in his applicatoin by supplying PHP code? – biphobe Mar 23 '16 at 09:32