Complex classes like HTTPClient often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use HTTPClient, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
100 | class HTTPClient { |
||
101 | //set these if you like |
||
102 | var $agent; // User agent |
||
103 | var $http; // HTTP version defaults to 1.0 |
||
104 | var $timeout; // read timeout (seconds) |
||
105 | var $cookies; |
||
106 | var $referer; |
||
107 | var $max_redirect; |
||
108 | var $max_bodysize; |
||
109 | var $max_bodysize_abort = true; // if set, abort if the response body is bigger than max_bodysize |
||
110 | var $header_regexp; // if set this RE must match against the headers, else abort |
||
111 | var $headers; |
||
112 | var $debug; |
||
113 | var $start = 0.0; // for timings |
||
114 | var $keep_alive = true; // keep alive rocks |
||
115 | |||
116 | // don't set these, read on error |
||
117 | var $error; |
||
118 | var $redirect_count; |
||
119 | |||
120 | // read these after a successful request |
||
121 | var $status; |
||
122 | var $resp_body; |
||
123 | var $resp_headers; |
||
124 | |||
125 | // set these to do basic authentication |
||
126 | var $user; |
||
127 | var $pass; |
||
128 | |||
129 | // set these if you need to use a proxy |
||
130 | var $proxy_host; |
||
131 | var $proxy_port; |
||
132 | var $proxy_user; |
||
133 | var $proxy_pass; |
||
134 | var $proxy_ssl; //boolean set to true if your proxy needs SSL |
||
135 | var $proxy_except; // regexp of URLs to exclude from proxy |
||
136 | |||
137 | // list of kept alive connections |
||
138 | static $connections = array(); |
||
139 | |||
140 | // what we use as boundary on multipart/form-data posts |
||
141 | var $boundary = '---DokuWikiHTTPClient--4523452351'; |
||
142 | |||
143 | /** |
||
144 | * Constructor. |
||
145 | * |
||
146 | * @author Andreas Gohr <[email protected]> |
||
147 | */ |
||
148 | function __construct(){ |
||
149 | $this->agent = 'Mozilla/4.0 (compatible; DokuWiki HTTP Client; '.PHP_OS.')'; |
||
150 | $this->timeout = 15; |
||
151 | $this->cookies = array(); |
||
152 | $this->referer = ''; |
||
153 | $this->max_redirect = 3; |
||
154 | $this->redirect_count = 0; |
||
155 | $this->status = 0; |
||
156 | $this->headers = array(); |
||
157 | $this->http = '1.0'; |
||
158 | $this->debug = false; |
||
159 | $this->max_bodysize = 0; |
||
160 | $this->header_regexp= ''; |
||
161 | if(extension_loaded('zlib')) $this->headers['Accept-encoding'] = 'gzip'; |
||
162 | $this->headers['Accept'] = 'text/xml,application/xml,application/xhtml+xml,'. |
||
163 | 'text/html,text/plain,image/png,image/jpeg,image/gif,*/*'; |
||
164 | $this->headers['Accept-Language'] = 'en-us'; |
||
165 | } |
||
166 | |||
167 | |||
168 | /** |
||
169 | * Simple function to do a GET request |
||
170 | * |
||
171 | * Returns the wanted page or false on an error; |
||
172 | * |
||
173 | * @param string $url The URL to fetch |
||
174 | * @param bool $sloppy304 Return body on 304 not modified |
||
175 | * @return false|string response body, false on error |
||
176 | * |
||
177 | * @author Andreas Gohr <[email protected]> |
||
178 | */ |
||
179 | function get($url,$sloppy304=false){ |
||
180 | if(!$this->sendRequest($url)) return false; |
||
181 | if($this->status == 304 && $sloppy304) return $this->resp_body; |
||
182 | if($this->status < 200 || $this->status > 206) return false; |
||
183 | return $this->resp_body; |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Simple function to do a GET request with given parameters |
||
188 | * |
||
189 | * Returns the wanted page or false on an error. |
||
190 | * |
||
191 | * This is a convenience wrapper around get(). The given parameters |
||
192 | * will be correctly encoded and added to the given base URL. |
||
193 | * |
||
194 | * @param string $url The URL to fetch |
||
195 | * @param array $data Associative array of parameters |
||
196 | * @param bool $sloppy304 Return body on 304 not modified |
||
197 | * @return false|string response body, false on error |
||
198 | * |
||
199 | * @author Andreas Gohr <[email protected]> |
||
200 | */ |
||
201 | function dget($url,$data,$sloppy304=false){ |
||
202 | if(strpos($url,'?')){ |
||
203 | $url .= '&'; |
||
204 | }else{ |
||
205 | $url .= '?'; |
||
206 | } |
||
207 | $url .= $this->_postEncode($data); |
||
208 | return $this->get($url,$sloppy304); |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * Simple function to do a POST request |
||
213 | * |
||
214 | * Returns the resulting page or false on an error; |
||
215 | * |
||
216 | * @param string $url The URL to fetch |
||
217 | * @param array $data Associative array of parameters |
||
218 | * @return false|string response body, false on error |
||
219 | * @author Andreas Gohr <[email protected]> |
||
220 | */ |
||
221 | function post($url,$data){ |
||
226 | |||
227 | /** |
||
228 | * Send an HTTP request |
||
229 | * |
||
230 | * This method handles the whole HTTP communication. It respects set proxy settings, |
||
231 | * builds the request headers, follows redirects and parses the response. |
||
232 | * |
||
233 | * Post data should be passed as associative array. When passed as string it will be |
||
234 | * sent as is. You will need to setup your own Content-Type header then. |
||
235 | * |
||
236 | * @param string $url - the complete URL |
||
237 | * @param mixed $data - the post data either as array or raw data |
||
238 | * @param string $method - HTTP Method usually GET or POST. |
||
239 | * @return bool - true on success |
||
240 | * |
||
241 | * @author Andreas Goetz <[email protected]> |
||
242 | * @author Andreas Gohr <[email protected]> |
||
243 | */ |
||
244 | function sendRequest($url,$data='',$method='GET'){ |
||
555 | |||
556 | /** |
||
557 | * Tries to establish a CONNECT tunnel via Proxy |
||
558 | * |
||
559 | * Protocol, Servername and Port will be stripped from the request URL when a successful CONNECT happened |
||
560 | * |
||
561 | * @param resource &$socket |
||
562 | * @param string &$requesturl |
||
563 | * @throws HTTPClientException when a tunnel is needed but could not be established |
||
564 | * @return bool true if a tunnel was established |
||
565 | */ |
||
566 | function _ssltunnel(&$socket, &$requesturl){ |
||
567 | if(!$this->proxy_host) return false; |
||
568 | $requestinfo = parse_url($requesturl); |
||
569 | if($requestinfo['scheme'] != 'https') return false; |
||
570 | if(!$requestinfo['port']) $requestinfo['port'] = 443; |
||
571 | |||
572 | // build request |
||
573 | $request = "CONNECT {$requestinfo['host']}:{$requestinfo['port']} HTTP/1.0".HTTP_NL; |
||
574 | $request .= "Host: {$requestinfo['host']}".HTTP_NL; |
||
575 | if($this->proxy_user) { |
||
576 | $request .= 'Proxy-Authorization: Basic '.base64_encode($this->proxy_user.':'.$this->proxy_pass).HTTP_NL; |
||
577 | } |
||
578 | $request .= HTTP_NL; |
||
579 | |||
580 | $this->_debug('SSL Tunnel CONNECT',$request); |
||
581 | $this->_sendData($socket, $request, 'SSL Tunnel CONNECT'); |
||
582 | |||
583 | // read headers from socket |
||
584 | $r_headers = ''; |
||
585 | do{ |
||
586 | $r_line = $this->_readLine($socket, 'headers'); |
||
587 | $r_headers .= $r_line; |
||
588 | }while($r_line != "\r\n" && $r_line != "\n"); |
||
589 | |||
590 | $this->_debug('SSL Tunnel Response',$r_headers); |
||
591 | if(preg_match('/^HTTP\/1\.[01] 200/i',$r_headers)){ |
||
592 | // set correct peer name for verification (enabled since PHP 5.6) |
||
593 | stream_context_set_option($socket, 'ssl', 'peer_name', $requestinfo['host']); |
||
594 | |||
595 | // because SSLv3 is mostly broken, we try TLS connections here first. |
||
596 | // according to https://github.com/splitbrain/dokuwiki/commit/c05ef534 we had problems with certain |
||
597 | // setups with this solution before, but we have no usable test for that and TLS should be the more |
||
598 | // common crypto by now |
||
599 | if (@stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { |
||
600 | $requesturl = $requestinfo['path']. |
||
601 | (!empty($requestinfo['query'])?'?'.$requestinfo['query']:''); |
||
602 | return true; |
||
603 | } |
||
604 | |||
605 | // if the above failed, this will most probably not work either, but we can try |
||
606 | if (@stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_SSLv3_CLIENT)) { |
||
607 | $requesturl = $requestinfo['path']. |
||
608 | (!empty($requestinfo['query'])?'?'.$requestinfo['query']:''); |
||
609 | return true; |
||
610 | } |
||
611 | |||
612 | throw new HTTPClientException('Failed to set up crypto for secure connection to '.$requestinfo['host'], -151); |
||
613 | } |
||
614 | |||
615 | throw new HTTPClientException('Failed to establish secure proxy connection', -150); |
||
616 | } |
||
617 | |||
618 | /** |
||
619 | * Safely write data to a socket |
||
620 | * |
||
621 | * @param resource $socket An open socket handle |
||
622 | * @param string $data The data to write |
||
623 | * @param string $message Description of what is being read |
||
624 | * @throws HTTPClientException |
||
625 | * |
||
626 | * @author Tom N Harris <[email protected]> |
||
627 | */ |
||
628 | function _sendData($socket, $data, $message) { |
||
629 | // send request |
||
630 | $towrite = strlen($data); |
||
631 | $written = 0; |
||
632 | while($written < $towrite){ |
||
633 | // check timeout |
||
634 | $time_used = $this->_time() - $this->start; |
||
635 | if($time_used > $this->timeout) |
||
636 | throw new HTTPClientException(sprintf('Timeout while sending %s (%.3fs)',$message, $time_used), -100); |
||
637 | if(feof($socket)) |
||
638 | throw new HTTPClientException("Socket disconnected while writing $message"); |
||
639 | |||
640 | // select parameters |
||
641 | $sel_r = null; |
||
642 | $sel_w = array($socket); |
||
643 | $sel_e = null; |
||
644 | // wait for stream ready or timeout (1sec) |
||
645 | if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){ |
||
646 | usleep(1000); |
||
647 | continue; |
||
648 | } |
||
649 | |||
650 | // write to stream |
||
651 | $nbytes = fwrite($socket, substr($data,$written,4096)); |
||
652 | if($nbytes === false) |
||
653 | throw new HTTPClientException("Failed writing to socket while sending $message", -100); |
||
654 | $written += $nbytes; |
||
655 | } |
||
656 | } |
||
657 | |||
658 | /** |
||
659 | * Safely read data from a socket |
||
660 | * |
||
661 | * Reads up to a given number of bytes or throws an exception if the |
||
662 | * response times out or ends prematurely. |
||
663 | * |
||
664 | * @param resource $socket An open socket handle in non-blocking mode |
||
665 | * @param int $nbytes Number of bytes to read |
||
666 | * @param string $message Description of what is being read |
||
667 | * @param bool $ignore_eof End-of-file is not an error if this is set |
||
668 | * @throws HTTPClientException |
||
669 | * @return string |
||
670 | * |
||
671 | * @author Tom N Harris <[email protected]> |
||
672 | */ |
||
673 | function _readData($socket, $nbytes, $message, $ignore_eof = false) { |
||
674 | $r_data = ''; |
||
675 | // Does not return immediately so timeout and eof can be checked |
||
676 | if ($nbytes < 0) $nbytes = 0; |
||
677 | $to_read = $nbytes; |
||
678 | do { |
||
679 | $time_used = $this->_time() - $this->start; |
||
680 | if ($time_used > $this->timeout) |
||
681 | throw new HTTPClientException( |
||
682 | sprintf('Timeout while reading %s after %d bytes (%.3fs)', $message, |
||
683 | strlen($r_data), $time_used), -100); |
||
684 | if(feof($socket)) { |
||
685 | if(!$ignore_eof) |
||
686 | throw new HTTPClientException("Premature End of File (socket) while reading $message"); |
||
687 | break; |
||
688 | } |
||
689 | |||
690 | if ($to_read > 0) { |
||
691 | // select parameters |
||
692 | $sel_r = array($socket); |
||
693 | $sel_w = null; |
||
694 | $sel_e = null; |
||
695 | // wait for stream ready or timeout (1sec) |
||
696 | if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){ |
||
697 | usleep(1000); |
||
698 | continue; |
||
699 | } |
||
700 | |||
701 | $bytes = fread($socket, $to_read); |
||
702 | if($bytes === false) |
||
703 | throw new HTTPClientException("Failed reading from socket while reading $message", -100); |
||
704 | $r_data .= $bytes; |
||
705 | $to_read -= strlen($bytes); |
||
706 | } |
||
707 | } while ($to_read > 0 && strlen($r_data) < $nbytes); |
||
708 | return $r_data; |
||
709 | } |
||
710 | |||
711 | /** |
||
712 | * Safely read a \n-terminated line from a socket |
||
713 | * |
||
714 | * Always returns a complete line, including the terminating \n. |
||
715 | * |
||
716 | * @param resource $socket An open socket handle in non-blocking mode |
||
717 | * @param string $message Description of what is being read |
||
718 | * @throws HTTPClientException |
||
719 | * @return string |
||
720 | * |
||
721 | * @author Tom N Harris <[email protected]> |
||
722 | */ |
||
723 | function _readLine($socket, $message) { |
||
724 | $r_data = ''; |
||
725 | do { |
||
726 | $time_used = $this->_time() - $this->start; |
||
727 | if ($time_used > $this->timeout) |
||
728 | throw new HTTPClientException( |
||
729 | sprintf('Timeout while reading %s (%.3fs) >%s<', $message, $time_used, $r_data), |
||
730 | -100); |
||
731 | if(feof($socket)) |
||
732 | throw new HTTPClientException("Premature End of File (socket) while reading $message"); |
||
733 | |||
734 | // select parameters |
||
735 | $sel_r = array($socket); |
||
736 | $sel_w = null; |
||
737 | $sel_e = null; |
||
738 | // wait for stream ready or timeout (1sec) |
||
739 | if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){ |
||
740 | usleep(1000); |
||
741 | continue; |
||
742 | } |
||
743 | |||
744 | $r_data = fgets($socket, 1024); |
||
745 | } while (!preg_match('/\n$/',$r_data)); |
||
746 | return $r_data; |
||
747 | } |
||
748 | |||
749 | /** |
||
750 | * print debug info |
||
751 | * |
||
752 | * Uses _debug_text or _debug_html depending on the SAPI name |
||
753 | * |
||
754 | * @author Andreas Gohr <[email protected]> |
||
755 | * |
||
756 | * @param string $info |
||
757 | * @param mixed $var |
||
758 | */ |
||
759 | function _debug($info,$var=null){ |
||
760 | if(!$this->debug) return; |
||
761 | if(php_sapi_name() == 'cli'){ |
||
762 | $this->_debug_text($info, $var); |
||
763 | }else{ |
||
764 | $this->_debug_html($info, $var); |
||
765 | } |
||
766 | } |
||
767 | |||
768 | /** |
||
769 | * print debug info as HTML |
||
770 | * |
||
771 | * @param string $info |
||
772 | * @param mixed $var |
||
773 | */ |
||
774 | function _debug_html($info, $var=null){ |
||
775 | print '<b>'.$info.'</b> '.($this->_time() - $this->start).'s<br />'; |
||
776 | if(!is_null($var)){ |
||
777 | ob_start(); |
||
778 | print_r($var); |
||
779 | $content = htmlspecialchars(ob_get_contents()); |
||
780 | ob_end_clean(); |
||
781 | print '<pre>'.$content.'</pre>'; |
||
782 | } |
||
783 | } |
||
784 | |||
785 | /** |
||
786 | * prints debug info as plain text |
||
787 | * |
||
788 | * @param string $info |
||
789 | * @param mixed $var |
||
790 | */ |
||
791 | function _debug_text($info, $var=null){ |
||
792 | print '*'.$info.'* '.($this->_time() - $this->start)."s\n"; |
||
793 | if(!is_null($var)) print_r($var); |
||
794 | print "\n-----------------------------------------------\n"; |
||
795 | } |
||
796 | |||
797 | /** |
||
798 | * Return current timestamp in microsecond resolution |
||
799 | * |
||
800 | * @return float |
||
801 | */ |
||
802 | static function _time(){ |
||
806 | |||
807 | /** |
||
808 | * convert given header string to Header array |
||
809 | * |
||
810 | * All Keys are lowercased. |
||
811 | * |
||
812 | * @author Andreas Gohr <[email protected]> |
||
813 | * |
||
814 | * @param string $string |
||
815 | * @return array |
||
816 | */ |
||
817 | function _parseHeaders($string){ |
||
818 | $headers = array(); |
||
819 | $lines = explode("\n",$string); |
||
820 | array_shift($lines); //skip first line (status) |
||
821 | foreach($lines as $line){ |
||
822 | @list($key, $val) = explode(':',$line,2); |
||
1 ignored issue
–
show
|
|||
823 | $key = trim($key); |
||
824 | $val = trim($val); |
||
825 | $key = strtolower($key); |
||
826 | if(!$key) continue; |
||
827 | if(isset($headers[$key])){ |
||
828 | if(is_array($headers[$key])){ |
||
829 | $headers[$key][] = $val; |
||
830 | }else{ |
||
831 | $headers[$key] = array($headers[$key],$val); |
||
832 | } |
||
833 | }else{ |
||
834 | $headers[$key] = $val; |
||
835 | } |
||
836 | } |
||
837 | return $headers; |
||
838 | } |
||
839 | |||
840 | /** |
||
841 | * convert given header array to header string |
||
842 | * |
||
843 | * @author Andreas Gohr <[email protected]> |
||
844 | * |
||
845 | * @param array $headers |
||
846 | * @return string |
||
847 | */ |
||
848 | function _buildHeaders($headers){ |
||
849 | $string = ''; |
||
850 | foreach($headers as $key => $value){ |
||
851 | if($value === '') continue; |
||
852 | $string .= $key.': '.$value.HTTP_NL; |
||
853 | } |
||
854 | return $string; |
||
855 | } |
||
856 | |||
857 | /** |
||
858 | * get cookies as http header string |
||
859 | * |
||
860 | * @author Andreas Goetz <[email protected]> |
||
861 | * |
||
862 | * @return string |
||
863 | */ |
||
864 | function _getCookies(){ |
||
865 | $headers = ''; |
||
866 | foreach ($this->cookies as $key => $val){ |
||
867 | $headers .= "$key=$val; "; |
||
868 | } |
||
869 | $headers = substr($headers, 0, -2); |
||
870 | if ($headers) $headers = "Cookie: $headers".HTTP_NL; |
||
871 | return $headers; |
||
872 | } |
||
873 | |||
874 | /** |
||
875 | * Encode data for posting |
||
876 | * |
||
877 | * @author Andreas Gohr <[email protected]> |
||
878 | * |
||
879 | * @param array $data |
||
880 | * @return string |
||
881 | */ |
||
882 | function _postEncode($data){ |
||
885 | |||
886 | /** |
||
887 | * Encode data for posting using multipart encoding |
||
888 | * |
||
889 | * @fixme use of urlencode might be wrong here |
||
890 | * @author Andreas Gohr <[email protected]> |
||
891 | * |
||
892 | * @param array $data |
||
893 | * @return string |
||
894 | */ |
||
895 | function _postMultipartEncode($data){ |
||
896 | $boundary = '--'.$this->boundary; |
||
897 | $out = ''; |
||
898 | foreach($data as $key => $val){ |
||
899 | $out .= $boundary.HTTP_NL; |
||
900 | if(!is_array($val)){ |
||
901 | $out .= 'Content-Disposition: form-data; name="'.urlencode($key).'"'.HTTP_NL; |
||
902 | $out .= HTTP_NL; // end of headers |
||
903 | $out .= $val; |
||
904 | $out .= HTTP_NL; |
||
905 | }else{ |
||
906 | $out .= 'Content-Disposition: form-data; name="'.urlencode($key).'"'; |
||
907 | if($val['filename']) $out .= '; filename="'.urlencode($val['filename']).'"'; |
||
908 | $out .= HTTP_NL; |
||
909 | if($val['mimetype']) $out .= 'Content-Type: '.$val['mimetype'].HTTP_NL; |
||
910 | $out .= HTTP_NL; // end of headers |
||
911 | $out .= $val['body']; |
||
912 | $out .= HTTP_NL; |
||
913 | } |
||
914 | } |
||
915 | $out .= "$boundary--".HTTP_NL; |
||
916 | return $out; |
||
917 | } |
||
918 | |||
919 | /** |
||
920 | * Generates a unique identifier for a connection. |
||
921 | * |
||
922 | * @param string $server |
||
923 | * @param string $port |
||
924 | * @return string unique identifier |
||
925 | */ |
||
926 | function _uniqueConnectionId($server, $port) { |
||
929 | } |
||
930 | |||
931 | //Setup VIM: ex: et ts=4 : |
||
932 |
Adding explicit visibility (
private
,protected
, orpublic
) is generally recommend to communicate to other developers how, and from where this method is intended to be used.