NGINX Reverse proxy problem accesing video streams

Forum for questions and support relating to the 1.32.x releases only.
Post Reply
chano
Posts: 2
Joined: Thu Sep 12, 2019 11:47 am

NGINX Reverse proxy problem accesing video streams

Post by chano » Thu Sep 12, 2019 12:06 pm

Hi, it might sounds like a extrange config but it makes a little sense form me.

I have an edge machine running Nginx as reverse proxy exposing an SSL connection to the Internet (on a non standard port) that proxies connection to my zoneminder docker which is working on port 80 without SSL.

Everything works fine but camera images. I get a broken link where camera image is supposed to be shown.

Digging in the problem, I realized that the IMG link that zoneminder places was in plain http (no https) and no port was placed.

I've studied the code and I finally found that in the includes path there is a Server.php file that holds two functions called Protocol and Port which mission is provide the correct "Protocol" and "Port" for the URL's.

So, to make things work I've made a couple changes: First, in Nginx I've added this two config lines to include in the HTTP Headers the original protocol and port the request had:

Code: Select all

           proxy_set_header X-Forwarded-Proto $scheme;
           proxy_set_header X-Forwarded-Port $server_port;
Then I've added at the end of Protocol function in Server.php this code:

Code: Select all

$headers = apache_request_headers();

    if (isset($headers['X-Forwarded-Proto'])) {
            return($headers['X-Forwarded-Proto']);
    }
And a very similar change at the end of Port function also in Server.php:

Code: Select all

   $headers = apache_request_headers();

   if (isset($headers['X-Forwarded-Port'])) {
       return($headers['X-Forwarded-Port']);
   }
So now, everything works like a charm!

I post this here in case it can help someone and it will be nice that this little change could be incorporated to the Zoneminder Code.

Thx
Chano.

Maximo1970
Posts: 37
Joined: Sun May 28, 2017 4:29 pm

Re: NGINX Reverse proxy problem accesing video streams

Post by Maximo1970 » Tue Oct 15, 2019 6:35 pm

Thanks for sharing, always good to see post like this.

I've just tried your configuration and it's not worked for my setup, I'm getting a http 500 error.

Cheers,

Garry

chano
Posts: 2
Joined: Thu Sep 12, 2019 11:47 am

Re: NGINX Reverse proxy problem accesing video streams

Post by chano » Wed Oct 16, 2019 12:31 pm

Hi @Maximo1970 an HTTP 500 error seems like a Syntax error in the code.

below you'll find my full Server.php (copy&pasted) from my Server. Is for Zoneminder version 1.32.3
Hope it helps!

Code: Select all

<?php
require_once('database.php');

$server_cache = array();

class Server {
  private $defaults = array(
    'Id'          => null,
    'Name'        => '',
    'Protocol'    => '',
    'Hostname'    => '',
    'Port'        =>  null,
    'PathToIndex' => '/zm/index.php',
    'PathToZMS'   => ZM_PATH_ZMS,
    'PathToApi'   => '/zm/api',
    'zmaudit'     => 1,
    'zmstats'     => 1,
    'zmtrigger'   => 0,
  );

  public function __construct($IdOrRow = NULL) {
    global $server_cache;
    $row = NULL;
    if ( $IdOrRow ) {
      if ( is_integer($IdOrRow) or ctype_digit($IdOrRow) ) {
        $row = dbFetchOne('SELECT * FROM Servers WHERE Id=?', NULL, array($IdOrRow));
        if ( !$row ) {
          Error('Unable to load Server record for Id='.$IdOrRow);
        }
      } elseif ( is_array($IdOrRow) ) {
        $row = $IdOrRow;
      }
    } # end if isset($IdOrRow)
    if ( $row ) {
      foreach ($row as $k => $v) {
        $this->{$k} = $v;
      }
      $server_cache[$row['Id']] = $this;
    } else {
      # Set defaults
      foreach ( $this->defaults as $k => $v ) $this->{$k} = $v;
    }
  }

  public function Hostname( $new = null ) {
    if ( $new != null )
      $this->{'Hostname'} = $new;

    if ( isset( $this->{'Hostname'}) and ( $this->{'Hostname'} != '' ) ) {
      return $this->{'Hostname'};
    } else if ( $this->Id() ) {
      return $this->{'Name'};
    }
    $result = explode(':',$_SERVER['HTTP_HOST']);
    return $result[0];
  }

  public function Protocol( $new = null ) {
    if ( $new != null )
      $this->{'Protocol'} = $new;

    if ( isset($this->{'Protocol'}) and ( $this->{'Protocol'} != '' ) ) {
      return $this->{'Protocol'};
    }

        $headers = apache_request_headers();

        if (isset($headers['X-Forwarded-Proto'])) {
		            return($headers['X-Forwarded-Proto']);
			        }

    return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? 'https' : 'http';
  }

  public function Port( $new = '' ) {
    if ( $new != '' )
      $this->{'Port'} = $new;

    if ( isset($this->{'Port'}) and $this->{'Port'} ) {
      return $this->{'Port'};
    }
    
       $headers = apache_request_headers();

       if (isset($headers['X-Forwarded-Port'])) {
	              return($headers['X-Forwarded-Port']);
		         }


    return $_SERVER['SERVER_PORT'];
  }

  public function PathToZMS( $new = null ) {
    if ( $new != null )
      $this{'PathToZMS'} = $new;
    if ( $this->Id() and $this->{'PathToZMS'} ) {
      return $this->{'PathToZMS'};
    } else {
      return ZM_PATH_ZMS;
    }
  }

  public function UrlToZMS( $port = null ) {
    return $this->Url($port).$this->PathToZMS();
  }

	public function Url( $port = null ) {
    $url = $this->Protocol().'://';
		$url .= $this->Hostname();
    if ( $port ) {
      $url .= ':'.$port;
    } else {
      $url .= ':'.$this->Port();
    }
    return $url;
	}

  public function PathToIndex( $new = null ) {
    if ( $new != null )
      $this->{'PathToIndex'} = $new;

    if ( isset($this->{'PathToIndex'}) and $this->{'PathToIndex'} ) {
      return $this->{'PathToIndex'};
    }
    return $_SERVER['PHP_SELF'];
  }

  public function UrlToIndex( $port=null ) {
    return $this->Url($port).$this->PathToIndex();
  }
  public function UrlToApi( $port=null ) {
    return $this->Url($port).$this->PathToApi();
  }
  public function PathToApi( $new = null ) {
    if ( $new != null )
      $this->{'PathToApi'} = $new;

    if ( isset($this->{'PathToApi'}) and $this->{'PathToApi'} ) {
      return $this->{'PathToApi'};
    }
    return '/zm/api';
  }

  public function __call($fn, array $args){
    if ( count($args) ) {
      $this->{$fn} = $args[0];
    }
    if ( array_key_exists($fn, $this) ) {
      return $this->{$fn};
    } else {
      if ( array_key_exists($fn, $this->defaults) ) {
        return $this->defaults{$fn};
      } else {
        $backTrace = debug_backtrace();
        $file = $backTrace[1]['file'];
        $line = $backTrace[1]['line'];
        Warning("Unknown function call Server->$fn from $file:$line");
      }
    }
  }
  public static function find( $parameters = null, $options = null ) {
    $filters = array();
    $sql = 'SELECT * FROM Servers ';
    $values = array();

    if ( $parameters ) {
      $fields = array();
      $sql .= 'WHERE ';
      foreach ( $parameters as $field => $value ) {
        if ( $value == null ) {
          $fields[] = $field.' IS NULL';
        } else if ( is_array( $value ) ) {
          $func = function(){return '?';};
          $fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')';
          $values += $value;

        } else {
          $fields[] = $field.'=?';
          $values[] = $value;
        }
      }
      $sql .= implode(' AND ', $fields );
    }
    if ( $options ) {
      if ( isset($options['order']) ) {
        $sql .= ' ORDER BY ' . $options['order'];
      }
      if ( isset($options['limit']) ) {
        if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) {
          $sql .= ' LIMIT ' . $options['limit'];
        } else {
          $backTrace = debug_backtrace();
          $file = $backTrace[1]['file'];
          $line = $backTrace[1]['line'];
          Error("Invalid value for limit(".$options['limit'].") passed to Server::find from $file:$line");
          return array();
        }
      }
    }
    $results = dbFetchAll( $sql, NULL, $values );
    if ( $results ) {
      return array_map(function($id){ return new Server($id); }, $results);
    }
    return array();
  }

  public static function find_one( $parameters = array() ) {
    global $server_cache;
    if ( 
        ( count($parameters) == 1 ) and
        isset($parameters['Id']) and
        isset($server_cache[$parameters['Id']]) ) {
      return $server_cache[$parameters['Id']];
    }
    $results = Server::find( $parameters, array('limit'=>1) );
    if ( ! sizeof($results) ) {
      return;
    }
    return $results[0];
  }

} # end class Server
?>

Maximo1970
Posts: 37
Joined: Sun May 28, 2017 4:29 pm

Re: NGINX Reverse proxy problem accesing video streams

Post by Maximo1970 » Wed Oct 16, 2019 7:46 pm

Thanks @chano that's sorted things. All working fine now.

Post Reply

Who is online

Users browsing this forum: No registered users and 3 guests