PageRenderTime 43ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/DDG/Rewrite.pm

https://gitlab.com/lanodan/duckduckgo
Perl | 172 lines | 127 code | 33 blank | 12 comment | 27 complexity | c2649d94b63d3805d2af14046b37e874 MD5 | raw file
Possible License(s): Apache-2.0
  1. package DDG::Rewrite;
  2. # ABSTRACT: A (mostly spice related) Rewrite definition in our system
  3. use Moo;
  4. use Carp qw( croak );
  5. use URI;
  6. sub BUILD {
  7. my ( $self ) = @_;
  8. my $to = $self->to;
  9. my $callback = $self->has_callback ? $self->callback : "";
  10. croak "Missing callback attribute for {{callback}} in to" if ($to =~ s/{{callback}}/$callback/g && !$self->has_callback);
  11. # Make sure we replace "{{dollar}}"" with "{dollar}".
  12. $to =~ s/{{dollar}}/\$\{dollar\}/g;
  13. my @missing_envs;
  14. for ($to =~ m/{{ENV{(\w+)}}}/g) {
  15. if (defined $ENV{$_}) {
  16. my $val = $ENV{$_};
  17. $to =~ s/{{ENV{$_}}}/$val/g;
  18. } else {
  19. push @missing_envs, $_;
  20. $to =~ s/{{ENV{$_}}}//g;
  21. }
  22. }
  23. $self->_missing_envs(\@missing_envs) if @missing_envs;
  24. $self->_parsed_to($to);
  25. }
  26. =head1 SYNOPSIS
  27. my $rewrite = DDG::Rewrite->new(
  28. path => '/js/test/',
  29. to => 'http://some.api/$1',
  30. );
  31. print $rewrite->nginx_conf;
  32. # location ^~ /js/test/ {
  33. # rewrite ^/js/test/(.*) /$1 break;
  34. # proxy_pass http://some.api:80/;
  35. # }
  36. my $missing_rewrite = DDG::Rewrite->new(
  37. path => '/js/test/',
  38. to => 'http://some.api/$1/?key={{ENV{DDGTEST_DDG_REWRITE_TEST_API_KEY}}}',
  39. );
  40. if ($missing_rewrite->missing_envs) { ... }
  41. # is false if $ENV{DDGTEST_DDG_REWRITE_TEST_API_KEY} is not set
  42. =head1 DESCRIPTION
  43. This class is used to contain a definition for a rewrite in our system. So far its specific
  44. designed for the problems we face towards spice redirects, but the definition is used in
  45. the L<App::DuckPAN> test server. In the production system we use those definitions to
  46. generate an L<nginx|http://duckduckgo.com/?q=nginx> config.
  47. =cut
  48. has path => (
  49. is => 'ro',
  50. required => 1,
  51. );
  52. has to => (
  53. is => 'ro',
  54. required => 1,
  55. );
  56. has from => (
  57. is => 'ro',
  58. predicate => 'has_from',
  59. );
  60. has callback => (
  61. is => 'ro',
  62. predicate => 'has_callback',
  63. );
  64. has wrap_jsonp_callback => (
  65. is => 'ro',
  66. default => sub { 0 },
  67. );
  68. has wrap_string_callback => (
  69. is => 'ro',
  70. default => sub { 0 },
  71. );
  72. has accept_header => (
  73. is => 'ro',
  74. default => sub { 0 },
  75. );
  76. has proxy_cache_valid => (
  77. is => 'ro',
  78. predicate => 'has_proxy_cache_valid',
  79. );
  80. has proxy_ssl_session_reuse => (
  81. is => 'ro',
  82. predicate => 'has_proxy_ssl_session_reuse',
  83. );
  84. has proxy_x_forwarded_for => (
  85. is => 'ro',
  86. default => sub { 'X-Forwarded-For $proxy_add_x_forwarded_for' }
  87. );
  88. has nginx_conf => (
  89. is => 'ro',
  90. lazy => 1,
  91. builder => '_build_nginx_conf',
  92. );
  93. sub _build_nginx_conf {
  94. my ( $self ) = @_;
  95. my $uri = URI->new($self->parsed_to);
  96. my $host = $uri->host;
  97. my $port = $uri->port;
  98. my $scheme = $uri->scheme;
  99. my $uri_path = $self->parsed_to;
  100. $uri_path =~ s!$scheme://$host:$port!!;
  101. $uri_path =~ s!$scheme://$host!!;
  102. my $is_duckduckgo = $host =~ /(?:127\.0\.0\.1|duckduckgo\.com)/;
  103. # wrap various other things into jsonp
  104. croak "Cannot use wrap_jsonp_callback and wrap_string callback at the same time!" if $self->wrap_jsonp_callback && $self->wrap_string_callback;
  105. my $wrap_jsonp_callback = $self->has_callback && $self->wrap_jsonp_callback;
  106. my $wrap_string_callback = $self->has_callback && $self->wrap_string_callback;
  107. my $uses_echo_module = $wrap_jsonp_callback || $wrap_string_callback;
  108. my $cfg = "location ^~ ".$self->path." {\n";
  109. $cfg .= "\tproxy_set_header Accept '".$self->accept_header."';\n" if $self->accept_header;
  110. # we need to make sure we have plain text coming back until we have a way
  111. # to unilaterally gunzip responses from the upstream since the echo module
  112. # will intersperse plaintext with gzip which results in encoding errors.
  113. # https://github.com/agentzh/echo-nginx-module/issues/30
  114. $cfg .= "\tproxy_set_header Accept-Encoding '';\n" if $uses_echo_module;
  115. if($uses_echo_module || $self->accept_header || $is_duckduckgo) {
  116. $cfg .= "\tinclude /usr/local/nginx/conf/nginx_inc_proxy_headers.conf;\n";
  117. }
  118. $cfg .= "\techo_before_body '".$self->callback."(';\n" if $wrap_jsonp_callback;
  119. $cfg .= "\techo_before_body '".$self->callback.qq|("';\n| if $wrap_string_callback;
  120. $cfg .= "\trewrite ^".$self->path.($self->has_from ? $self->from : "(.*)")." ".$uri_path." break;\n";
  121. $cfg .= "\tproxy_pass ".$scheme."://".$host.":".$port."/;\n";
  122. $cfg .= "\tproxy_set_header ".$self->proxy_x_forwarded_for.";\n" if $is_duckduckgo;
  123. $cfg .= "\tproxy_cache_valid ".$self->proxy_cache_valid.";\n" if $self->has_proxy_cache_valid;
  124. $cfg .= "\tproxy_ssl_session_reuse ".$self->proxy_ssl_session_reuse.";\n" if $self->has_proxy_ssl_session_reuse;
  125. $cfg .= "\techo_after_body ');';\n" if $wrap_jsonp_callback;
  126. $cfg .= "\techo_after_body '\");';\n" if $wrap_string_callback;
  127. $cfg .= "}\n";
  128. return $cfg;
  129. }
  130. has _missing_envs => (
  131. is => 'rw',
  132. predicate => 'has_missing_envs',
  133. );
  134. sub missing_envs { shift->_missing_envs }
  135. has _parsed_to => (
  136. is => 'rw',
  137. );
  138. sub parsed_to { shift->_parsed_to }
  139. 1;