Это на самом деле довольно сложная проблема. И причина того, что это сложно, состоит в том, что различные части URL-адреса должны обрабатываться (кодироваться) по-разному.
По моему опыту, лучший способ сделать это - собрать URL из его компонентов, используя класс URL или URI, позволяя им правильно позаботиться о кодировании компонентов.
На самом деле, теперь, когда я думаю об этом, вам нужно кодировать компоненты, прежде чем они будут собраны. После того, как детали собраны, невозможно сказать, является ли (например) "?" предназначен для разделителя запросов (не экранируйте его) или символа в компоненте пути (экранируйте его).